futurism 1.2.0.pre8 → 1.2.0.pre11
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Appraisals +24 -0
- data/Appraisals~ +24 -0
- data/CHANGELOG.md +370 -0
- data/Gemfile +15 -0
- data/Gemfile.lock +215 -0
- data/Gemfile~ +17 -0
- data/README.md +44 -9
- data/README.md~ +405 -0
- data/app/assets/javascripts/futurism.js +191 -0
- data/app/assets/javascripts/futurism.min.js +2 -0
- data/app/assets/javascripts/futurism.min.js.map +1 -0
- data/app/assets/javascripts/futurism.umd.js +188 -0
- data/app/assets/javascripts/futurism.umd.min.js +2 -0
- data/app/assets/javascripts/futurism.umd.min.js.map +1 -0
- data/{lib → app/channels}/futurism/channel.rb +1 -1
- data/bin/rails +25 -0
- data/bin/standardize +5 -0
- data/bin/test +5 -0
- data/futurism.gemspec +38 -0
- data/futurism.gemspec~ +30 -0
- data/lib/futurism/configuration.rb +21 -0
- data/lib/futurism/engine.rb +19 -0
- data/lib/futurism/helpers.rb +2 -2
- data/lib/futurism/importmap.rb +2 -0
- data/lib/futurism/version.rb +1 -1
- data/lib/futurism.rb +2 -1
- data/package.json +47 -0
- data/package.json~ +51 -0
- data/rollup.config.js +77 -0
- data/rollup.config.js~ +73 -0
- data/test/cable/channel_test.rb +319 -0
- data/test/dummy/app/channels/application_cable/channel.rb +4 -0
- data/test/dummy/app/channels/application_cable/connection.rb +4 -0
- data/test/dummy/app/controllers/application_controller.rb +2 -0
- data/test/dummy/app/controllers/home_controller.rb +6 -0
- data/test/dummy/app/controllers/posts_controller.rb +59 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/helpers/posts_helper.rb +2 -0
- data/test/dummy/app/jobs/application_job.rb +7 -0
- data/test/dummy/app/models/action_item.rb +2 -0
- data/test/dummy/app/models/application_record.rb +3 -0
- data/test/dummy/app/models/post.rb +2 -0
- data/test/dummy/config/application.rb +29 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +40 -0
- data/test/dummy/config/environments/production.rb +94 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/content_security_policy.rb +28 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
- data/test/dummy/config/puma.rb +38 -0
- data/test/dummy/config/routes.rb +12 -0
- data/test/dummy/config/spring.rb +6 -0
- data/test/dummy/db/migrate/20200711122838_create_posts.rb +9 -0
- data/test/dummy/db/migrate/2021042923813_create_action_items.rb +9 -0
- data/test/dummy/db/schema.rb +27 -0
- data/test/futurism_test.rb +28 -0
- data/test/helper/helper_test.rb +206 -0
- data/test/integration/navigation_test.rb +7 -0
- data/test/resolver/controller/renderer_test.rb +120 -0
- data/test/resolver/controller_test.rb +26 -0
- data/test/test_helper.rb +14 -0
- data/yarn.lock +3263 -0
- metadata +122 -18
- data/config/routes.rb +0 -2
- data/lib/futurism/channel.rb~ +0 -31
- data/lib/futurism/helpers.rb~ +0 -112
- data/lib/futurism/options_transformer.rb~ +0 -0
- data/lib/futurism/resolver/controller/renderer.rb~ +0 -76
- data/lib/futurism/resolver/controller.rb~ +0 -17
- data/lib/futurism/resolver/resources.rb~ +0 -101
- data/lib/futurism/version.rb~ +0 -3
- data/lib/futurism.rb~ +0 -30
- data/lib/tasks/futurism_tasks.rake +0 -39
- data/lib/tasks/futurism_tasks.rake~ +0 -39
@@ -0,0 +1,191 @@
|
|
1
|
+
import CableReady from "cable_ready";
|
2
|
+
|
3
|
+
const debounceEvents = (callback, delay = 20) => {
|
4
|
+
let timeoutId;
|
5
|
+
let events = [];
|
6
|
+
return (...args) => {
|
7
|
+
clearTimeout(timeoutId);
|
8
|
+
events = [ ...events, ...args ];
|
9
|
+
timeoutId = setTimeout((() => {
|
10
|
+
timeoutId = null;
|
11
|
+
callback(events);
|
12
|
+
events = [];
|
13
|
+
}), delay);
|
14
|
+
};
|
15
|
+
};
|
16
|
+
|
17
|
+
const createSubscription = consumer => {
|
18
|
+
consumer.subscriptions.create("Futurism::Channel", {
|
19
|
+
connected() {
|
20
|
+
window.Futurism = this;
|
21
|
+
document.addEventListener("futurism:appear", debounceEvents((events => {
|
22
|
+
this.send({
|
23
|
+
signed_params: events.map((e => e.target.dataset.signedParams)),
|
24
|
+
sgids: events.map((e => e.target.dataset.sgid)),
|
25
|
+
signed_controllers: events.map((e => e.target.dataset.signedController)),
|
26
|
+
urls: events.map((_ => window.location.href)),
|
27
|
+
broadcast_each: events.map((e => e.target.dataset.broadcastEach))
|
28
|
+
});
|
29
|
+
})));
|
30
|
+
},
|
31
|
+
received(data) {
|
32
|
+
if (data.cableReady) {
|
33
|
+
CableReady.perform(data.operations, {
|
34
|
+
emitMissingElementWarnings: false
|
35
|
+
});
|
36
|
+
document.dispatchEvent(new CustomEvent("futurism:appeared", {
|
37
|
+
bubbles: true,
|
38
|
+
cancelable: true
|
39
|
+
}));
|
40
|
+
}
|
41
|
+
}
|
42
|
+
});
|
43
|
+
};
|
44
|
+
|
45
|
+
const dispatchAppearEvent = (entry, observer = null) => {
|
46
|
+
if (!window.Futurism) {
|
47
|
+
return () => {
|
48
|
+
setTimeout((() => dispatchAppearEvent(entry, observer)()), 1);
|
49
|
+
};
|
50
|
+
}
|
51
|
+
const target = entry.target ? entry.target : entry;
|
52
|
+
const evt = new CustomEvent("futurism:appear", {
|
53
|
+
bubbles: true,
|
54
|
+
detail: {
|
55
|
+
target: target,
|
56
|
+
observer: observer
|
57
|
+
}
|
58
|
+
});
|
59
|
+
return () => {
|
60
|
+
target.dispatchEvent(evt);
|
61
|
+
};
|
62
|
+
};
|
63
|
+
|
64
|
+
const wait = ms => new Promise((resolve => setTimeout(resolve, ms)));
|
65
|
+
|
66
|
+
const callWithRetry = async (fn, depth = 0) => {
|
67
|
+
try {
|
68
|
+
return await fn();
|
69
|
+
} catch (e) {
|
70
|
+
if (depth > 10) {
|
71
|
+
throw e;
|
72
|
+
}
|
73
|
+
await wait(1.15 ** depth * 2e3);
|
74
|
+
return callWithRetry(fn, depth + 1);
|
75
|
+
}
|
76
|
+
};
|
77
|
+
|
78
|
+
const observerCallback = (entries, observer) => {
|
79
|
+
entries.forEach((async entry => {
|
80
|
+
if (!entry.isIntersecting) return;
|
81
|
+
await callWithRetry(dispatchAppearEvent(entry, observer));
|
82
|
+
}));
|
83
|
+
};
|
84
|
+
|
85
|
+
const extendElementWithIntersectionObserver = element => {
|
86
|
+
Object.assign(element, {
|
87
|
+
observer: new IntersectionObserver(observerCallback.bind(element), {})
|
88
|
+
});
|
89
|
+
if (!element.hasAttribute("keep")) {
|
90
|
+
element.observer.observe(element);
|
91
|
+
}
|
92
|
+
};
|
93
|
+
|
94
|
+
const extendElementWithEagerLoading = element => {
|
95
|
+
if (element.dataset.eager === "true") {
|
96
|
+
if (element.observer) element.observer.disconnect();
|
97
|
+
callWithRetry(dispatchAppearEvent(element));
|
98
|
+
}
|
99
|
+
};
|
100
|
+
|
101
|
+
class FuturismElement extends HTMLElement {
|
102
|
+
connectedCallback() {
|
103
|
+
extendElementWithIntersectionObserver(this);
|
104
|
+
extendElementWithEagerLoading(this);
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
class FuturismTableRow extends HTMLTableRowElement {
|
109
|
+
connectedCallback() {
|
110
|
+
extendElementWithIntersectionObserver(this);
|
111
|
+
extendElementWithEagerLoading(this);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
|
115
|
+
class FuturismLI extends HTMLLIElement {
|
116
|
+
connectedCallback() {
|
117
|
+
extendElementWithIntersectionObserver(this);
|
118
|
+
extendElementWithEagerLoading(this);
|
119
|
+
}
|
120
|
+
}
|
121
|
+
|
122
|
+
async function sha256(message) {
|
123
|
+
const msgBuffer = new TextEncoder("utf-8").encode(message);
|
124
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
125
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
126
|
+
const hashHex = hashArray.map((b => ("00" + b.toString(16)).slice(-2))).join("");
|
127
|
+
return hashHex;
|
128
|
+
}
|
129
|
+
|
130
|
+
const polyfillCustomElements = () => {
|
131
|
+
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
132
|
+
if (customElements) {
|
133
|
+
if (isSafari) {
|
134
|
+
document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>');
|
135
|
+
} else {
|
136
|
+
try {
|
137
|
+
customElements.define("built-in", document.createElement("tr").constructor, {
|
138
|
+
extends: "tr"
|
139
|
+
});
|
140
|
+
} catch (_) {
|
141
|
+
document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>');
|
142
|
+
}
|
143
|
+
}
|
144
|
+
} else {
|
145
|
+
document.write('<script src="//unpkg.com/document-register-element"><\/script>');
|
146
|
+
}
|
147
|
+
};
|
148
|
+
|
149
|
+
const defineElements = e => {
|
150
|
+
if (!customElements.get("futurism-element")) {
|
151
|
+
customElements.define("futurism-element", FuturismElement);
|
152
|
+
customElements.define("futurism-table-row", FuturismTableRow, {
|
153
|
+
extends: "tr"
|
154
|
+
});
|
155
|
+
customElements.define("futurism-li", FuturismLI, {
|
156
|
+
extends: "li"
|
157
|
+
});
|
158
|
+
}
|
159
|
+
};
|
160
|
+
|
161
|
+
const cachePlaceholders = e => {
|
162
|
+
sha256(e.detail.element.outerHTML).then((hashedContent => {
|
163
|
+
e.detail.element.setAttribute("keep", "");
|
164
|
+
sessionStorage.setItem(`futurism-${hashedContent}`, e.detail.element.outerHTML);
|
165
|
+
e.target.dataset.futurismHash = hashedContent;
|
166
|
+
}));
|
167
|
+
};
|
168
|
+
|
169
|
+
const restorePlaceholders = e => {
|
170
|
+
const inNamespace = ([key, _payload]) => key.startsWith("futurism-");
|
171
|
+
Object.entries(sessionStorage).filter(inNamespace).forEach((([key, payload]) => {
|
172
|
+
const match = /^futurism-(.*)/.exec(key);
|
173
|
+
const targetElement = document.querySelector(`[data-futurism-hash="${match[1]}"]`);
|
174
|
+
if (targetElement) {
|
175
|
+
targetElement.outerHTML = payload;
|
176
|
+
sessionStorage.removeItem(key);
|
177
|
+
}
|
178
|
+
}));
|
179
|
+
};
|
180
|
+
|
181
|
+
const initializeElements = () => {
|
182
|
+
polyfillCustomElements();
|
183
|
+
document.addEventListener("DOMContentLoaded", defineElements);
|
184
|
+
document.addEventListener("turbo:load", defineElements);
|
185
|
+
document.addEventListener("turbo:before-cache", restorePlaceholders);
|
186
|
+
document.addEventListener("turbolinks:load", defineElements);
|
187
|
+
document.addEventListener("turbolinks:before-cache", restorePlaceholders);
|
188
|
+
document.addEventListener("cable-ready:after-outer-html", cachePlaceholders);
|
189
|
+
};
|
190
|
+
|
191
|
+
export { createSubscription, initializeElements };
|
@@ -0,0 +1,2 @@
|
|
1
|
+
import e from"cable_ready";const t=t=>{t.subscriptions.create("Futurism::Channel",{connected(){window.Futurism=this,document.addEventListener("futurism:appear",((e,t=20)=>{let s,r=[];return(...n)=>{clearTimeout(s),r=[...r,...n],s=setTimeout((()=>{s=null,e(r),r=[]}),t)}})((e=>{this.send({signed_params:e.map((e=>e.target.dataset.signedParams)),sgids:e.map((e=>e.target.dataset.sgid)),signed_controllers:e.map((e=>e.target.dataset.signedController)),urls:e.map((e=>window.location.href)),broadcast_each:e.map((e=>e.target.dataset.broadcastEach))})})))},received(t){t.cableReady&&(e.perform(t.operations,{emitMissingElementWarnings:!1}),document.dispatchEvent(new CustomEvent("futurism:appeared",{bubbles:!0,cancelable:!0})))}})},s=(e,t=null)=>{if(!window.Futurism)return()=>{setTimeout((()=>s(e,t)()),1)};const r=e.target?e.target:e,n=new CustomEvent("futurism:appear",{bubbles:!0,detail:{target:r,observer:t}});return()=>{r.dispatchEvent(n)}},r=async(e,t=0)=>{try{return await e()}catch(n){if(t>10)throw n;return await(s=1.15**t*2e3,new Promise((e=>setTimeout(e,s)))),r(e,t+1)}var s},n=(e,t)=>{e.forEach((async e=>{e.isIntersecting&&await r(s(e,t))}))},a=e=>{Object.assign(e,{observer:new IntersectionObserver(n.bind(e),{})}),e.hasAttribute("keep")||e.observer.observe(e)},i=e=>{"true"===e.dataset.eager&&(e.observer&&e.observer.disconnect(),r(s(e)))};class o extends HTMLElement{connectedCallback(){a(this),i(this)}}class c extends HTMLTableRowElement{connectedCallback(){a(this),i(this)}}class u extends HTMLLIElement{connectedCallback(){a(this),i(this)}}const m=e=>{customElements.get("futurism-element")||(customElements.define("futurism-element",o),customElements.define("futurism-table-row",c,{extends:"tr"}),customElements.define("futurism-li",u,{extends:"li"}))},d=e=>{(async function(e){const t=new TextEncoder("utf-8").encode(e),s=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(s)).map((e=>("00"+e.toString(16)).slice(-2))).join("")})(e.detail.element.outerHTML).then((t=>{e.detail.element.setAttribute("keep",""),sessionStorage.setItem(`futurism-${t}`,e.detail.element.outerHTML),e.target.dataset.futurismHash=t}))},l=e=>{Object.entries(sessionStorage).filter((([e,t])=>e.startsWith("futurism-"))).forEach((([e,t])=>{const s=/^futurism-(.*)/.exec(e),r=document.querySelector(`[data-futurism-hash="${s[1]}"]`);r&&(r.outerHTML=t,sessionStorage.removeItem(e))}))},b=()=>{(()=>{const e=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);if(customElements)if(e)document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>');else try{customElements.define("built-in",document.createElement("tr").constructor,{extends:"tr"})}catch(e){document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>')}else document.write('<script src="//unpkg.com/document-register-element"><\/script>')})(),document.addEventListener("DOMContentLoaded",m),document.addEventListener("turbo:load",m),document.addEventListener("turbo:before-cache",l),document.addEventListener("turbolinks:load",m),document.addEventListener("turbolinks:before-cache",l),document.addEventListener("cable-ready:after-outer-html",d)};export{t as createSubscription,b as initializeElements};
|
2
|
+
//# sourceMappingURL=futurism.min.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"futurism.min.js","sources":["../../../javascript/futurism_channel.js","../../../javascript/elements/futurism_utils.js","../../../javascript/elements/futurism_element.js","../../../javascript/elements/futurism_table_row.js","../../../javascript/elements/futurism_li.js","../../../javascript/elements/index.js","../../../javascript/utils/crypto.js"],"sourcesContent":["/* global CustomEvent, setTimeout */\n\nimport CableReady from 'cable_ready'\n\nconst debounceEvents = (callback, delay = 20) => {\n let timeoutId\n let events = []\n return (...args) => {\n clearTimeout(timeoutId)\n events = [...events, ...args]\n timeoutId = setTimeout(() => {\n timeoutId = null\n callback(events)\n events = []\n }, delay)\n }\n}\n\nexport const createSubscription = consumer => {\n consumer.subscriptions.create('Futurism::Channel', {\n connected () {\n window.Futurism = this\n document.addEventListener(\n 'futurism:appear',\n debounceEvents(events => {\n this.send({\n signed_params: events.map(e => e.target.dataset.signedParams),\n sgids: events.map(e => e.target.dataset.sgid),\n signed_controllers: events.map(\n e => e.target.dataset.signedController\n ),\n urls: events.map(_ => window.location.href),\n broadcast_each: events.map(e => e.target.dataset.broadcastEach)\n })\n })\n )\n },\n\n received (data) {\n if (data.cableReady) {\n CableReady.perform(data.operations, {\n emitMissingElementWarnings: false\n })\n\n document.dispatchEvent(\n new CustomEvent('futurism:appeared', {\n bubbles: true,\n cancelable: true\n })\n )\n }\n }\n })\n}\n","/* global IntersectionObserver, CustomEvent, setTimeout */\n\nconst dispatchAppearEvent = (entry, observer = null) => {\n if (!window.Futurism) {\n return () => {\n setTimeout(() => dispatchAppearEvent(entry, observer)(), 1)\n }\n }\n\n const target = entry.target ? entry.target : entry\n\n const evt = new CustomEvent('futurism:appear', {\n bubbles: true,\n detail: {\n target,\n observer\n }\n })\n\n return () => {\n target.dispatchEvent(evt)\n }\n}\n\n// from https://advancedweb.hu/how-to-implement-an-exponential-backoff-retry-strategy-in-javascript/#rejection-based-retrying\nconst wait = ms => new Promise(resolve => setTimeout(resolve, ms))\n\nconst callWithRetry = async (fn, depth = 0) => {\n try {\n return await fn()\n } catch (e) {\n if (depth > 10) {\n throw e\n }\n await wait(1.15 ** depth * 2000)\n\n return callWithRetry(fn, depth + 1)\n }\n}\n\nconst observerCallback = (entries, observer) => {\n entries.forEach(async entry => {\n if (!entry.isIntersecting) return\n\n await callWithRetry(dispatchAppearEvent(entry, observer))\n })\n}\n\nexport const extendElementWithIntersectionObserver = element => {\n Object.assign(element, {\n observer: new IntersectionObserver(observerCallback.bind(element), {})\n })\n\n if (!element.hasAttribute('keep')) {\n element.observer.observe(element)\n }\n}\n\nexport const extendElementWithEagerLoading = element => {\n if (element.dataset.eager === 'true') {\n if (element.observer) element.observer.disconnect()\n callWithRetry(dispatchAppearEvent(element))\n }\n}\n","/* global HTMLElement */\n\nimport {\n extendElementWithIntersectionObserver,\n extendElementWithEagerLoading\n} from './futurism_utils'\n\nexport default class FuturismElement extends HTMLElement {\n connectedCallback () {\n extendElementWithIntersectionObserver(this)\n extendElementWithEagerLoading(this)\n }\n}\n","/* global HTMLTableRowElement */\n\nimport {\n extendElementWithIntersectionObserver,\n extendElementWithEagerLoading\n} from './futurism_utils'\n\nexport default class FuturismTableRow extends HTMLTableRowElement {\n connectedCallback () {\n extendElementWithIntersectionObserver(this)\n extendElementWithEagerLoading(this)\n }\n}\n","/* global HTMLLIElement */\r\n\r\nimport {\r\n extendElementWithIntersectionObserver,\r\n extendElementWithEagerLoading\r\n} from './futurism_utils'\r\n\r\nexport default class FuturismLI extends HTMLLIElement {\r\n connectedCallback () {\r\n extendElementWithIntersectionObserver(this)\r\n extendElementWithEagerLoading(this)\r\n }\r\n}\r\n","/* global customElements, sessionStorage */\n\nimport FuturismElement from './futurism_element'\nimport FuturismTableRow from './futurism_table_row'\nimport FuturismLI from './futurism_li'\n\nimport { sha256 } from '../utils/crypto'\n\nconst polyfillCustomElements = () => {\n const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)\n\n if (customElements) {\n if (isSafari) {\n document.write(\n '<script src=\"//unpkg.com/@ungap/custom-elements-builtin\"><\\x2fscript>'\n )\n } else {\n try {\n customElements.define(\n 'built-in',\n document.createElement('tr').constructor,\n { extends: 'tr' }\n )\n } catch (_) {\n document.write(\n '<script src=\"//unpkg.com/@ungap/custom-elements-builtin\"><\\x2fscript>'\n )\n }\n }\n } else {\n document.write(\n '<script src=\"//unpkg.com/document-register-element\"><\\x2fscript>'\n )\n }\n}\n\nconst defineElements = e => {\n if (!customElements.get('futurism-element')) {\n customElements.define('futurism-element', FuturismElement)\n customElements.define('futurism-table-row', FuturismTableRow, {\n extends: 'tr'\n })\n customElements.define('futurism-li', FuturismLI, { extends: 'li' })\n }\n}\n\nconst cachePlaceholders = e => {\n sha256(e.detail.element.outerHTML).then(hashedContent => {\n e.detail.element.setAttribute('keep', '')\n sessionStorage.setItem(\n `futurism-${hashedContent}`,\n e.detail.element.outerHTML\n )\n e.target.dataset.futurismHash = hashedContent\n })\n}\n\nconst restorePlaceholders = e => {\n const inNamespace = ([key, _payload]) => key.startsWith('futurism-')\n Object.entries(sessionStorage)\n .filter(inNamespace)\n .forEach(([key, payload]) => {\n const match = /^futurism-(.*)/.exec(key)\n const targetElement = document.querySelector(\n `[data-futurism-hash=\"${match[1]}\"]`\n )\n\n if (targetElement) {\n targetElement.outerHTML = payload\n sessionStorage.removeItem(key)\n }\n })\n}\n\nexport const initializeElements = () => {\n polyfillCustomElements()\n document.addEventListener('DOMContentLoaded', defineElements)\n document.addEventListener('turbo:load', defineElements)\n document.addEventListener('turbo:before-cache', restorePlaceholders)\n document.addEventListener('turbolinks:load', defineElements)\n document.addEventListener('turbolinks:before-cache', restorePlaceholders)\n document.addEventListener('cable-ready:after-outer-html', cachePlaceholders)\n}\n","/* global crypto */\n\nexport async function sha256 (message) {\n // encode as UTF-8\n const msgBuffer = new TextEncoder('utf-8').encode(message)\n\n // hash the message\n const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer)\n\n // convert ArrayBuffer to Array\n const hashArray = Array.from(new Uint8Array(hashBuffer))\n\n // convert bytes to hex string\n const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('')\n\n return hashHex\n}\n"],"names":["createSubscription","consumer","subscriptions","create","connected","window","Futurism","this","document","addEventListener","callback","delay","timeoutId","events","args","clearTimeout","setTimeout","debounceEvents","send","signed_params","map","e","target","dataset","signedParams","sgids","sgid","signed_controllers","signedController","urls","_","location","href","broadcast_each","broadcastEach","received","data","cableReady","CableReady","perform","operations","emitMissingElementWarnings","dispatchEvent","CustomEvent","bubbles","cancelable","dispatchAppearEvent","entry","observer","evt","detail","callWithRetry","async","fn","depth","ms","Promise","resolve","observerCallback","entries","forEach","isIntersecting","extendElementWithIntersectionObserver","element","Object","assign","IntersectionObserver","bind","hasAttribute","observe","extendElementWithEagerLoading","eager","disconnect","FuturismElement","HTMLElement","connectedCallback","FuturismTableRow","HTMLTableRowElement","FuturismLI","HTMLLIElement","defineElements","customElements","get","define","extends","cachePlaceholders","message","msgBuffer","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","b","toString","slice","join","sha256","outerHTML","then","hashedContent","setAttribute","sessionStorage","setItem","futurismHash","restorePlaceholders","filter","key","_payload","startsWith","payload","match","exec","targetElement","querySelector","removeItem","initializeElements","isSafari","test","navigator","userAgent","write","createElement","constructor","polyfillCustomElements"],"mappings":"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"}
|
@@ -0,0 +1,188 @@
|
|
1
|
+
(function(global, factory) {
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("cable_ready")) : typeof define === "function" && define.amd ? define([ "exports", "cable_ready" ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
|
3
|
+
factory(global.Futurism = {}, global.CableReady));
|
4
|
+
})(this, (function(exports, CableReady) {
|
5
|
+
"use strict";
|
6
|
+
function _interopDefaultLegacy(e) {
|
7
|
+
return e && typeof e === "object" && "default" in e ? e : {
|
8
|
+
default: e
|
9
|
+
};
|
10
|
+
}
|
11
|
+
var CableReady__default = _interopDefaultLegacy(CableReady);
|
12
|
+
const debounceEvents = (callback, delay = 20) => {
|
13
|
+
let timeoutId;
|
14
|
+
let events = [];
|
15
|
+
return (...args) => {
|
16
|
+
clearTimeout(timeoutId);
|
17
|
+
events = [ ...events, ...args ];
|
18
|
+
timeoutId = setTimeout((() => {
|
19
|
+
timeoutId = null;
|
20
|
+
callback(events);
|
21
|
+
events = [];
|
22
|
+
}), delay);
|
23
|
+
};
|
24
|
+
};
|
25
|
+
const createSubscription = consumer => {
|
26
|
+
consumer.subscriptions.create("Futurism::Channel", {
|
27
|
+
connected() {
|
28
|
+
window.Futurism = this;
|
29
|
+
document.addEventListener("futurism:appear", debounceEvents((events => {
|
30
|
+
this.send({
|
31
|
+
signed_params: events.map((e => e.target.dataset.signedParams)),
|
32
|
+
sgids: events.map((e => e.target.dataset.sgid)),
|
33
|
+
signed_controllers: events.map((e => e.target.dataset.signedController)),
|
34
|
+
urls: events.map((_ => window.location.href)),
|
35
|
+
broadcast_each: events.map((e => e.target.dataset.broadcastEach))
|
36
|
+
});
|
37
|
+
})));
|
38
|
+
},
|
39
|
+
received(data) {
|
40
|
+
if (data.cableReady) {
|
41
|
+
CableReady__default["default"].perform(data.operations, {
|
42
|
+
emitMissingElementWarnings: false
|
43
|
+
});
|
44
|
+
document.dispatchEvent(new CustomEvent("futurism:appeared", {
|
45
|
+
bubbles: true,
|
46
|
+
cancelable: true
|
47
|
+
}));
|
48
|
+
}
|
49
|
+
}
|
50
|
+
});
|
51
|
+
};
|
52
|
+
const dispatchAppearEvent = (entry, observer = null) => {
|
53
|
+
if (!window.Futurism) {
|
54
|
+
return () => {
|
55
|
+
setTimeout((() => dispatchAppearEvent(entry, observer)()), 1);
|
56
|
+
};
|
57
|
+
}
|
58
|
+
const target = entry.target ? entry.target : entry;
|
59
|
+
const evt = new CustomEvent("futurism:appear", {
|
60
|
+
bubbles: true,
|
61
|
+
detail: {
|
62
|
+
target: target,
|
63
|
+
observer: observer
|
64
|
+
}
|
65
|
+
});
|
66
|
+
return () => {
|
67
|
+
target.dispatchEvent(evt);
|
68
|
+
};
|
69
|
+
};
|
70
|
+
const wait = ms => new Promise((resolve => setTimeout(resolve, ms)));
|
71
|
+
const callWithRetry = async (fn, depth = 0) => {
|
72
|
+
try {
|
73
|
+
return await fn();
|
74
|
+
} catch (e) {
|
75
|
+
if (depth > 10) {
|
76
|
+
throw e;
|
77
|
+
}
|
78
|
+
await wait(1.15 ** depth * 2e3);
|
79
|
+
return callWithRetry(fn, depth + 1);
|
80
|
+
}
|
81
|
+
};
|
82
|
+
const observerCallback = (entries, observer) => {
|
83
|
+
entries.forEach((async entry => {
|
84
|
+
if (!entry.isIntersecting) return;
|
85
|
+
await callWithRetry(dispatchAppearEvent(entry, observer));
|
86
|
+
}));
|
87
|
+
};
|
88
|
+
const extendElementWithIntersectionObserver = element => {
|
89
|
+
Object.assign(element, {
|
90
|
+
observer: new IntersectionObserver(observerCallback.bind(element), {})
|
91
|
+
});
|
92
|
+
if (!element.hasAttribute("keep")) {
|
93
|
+
element.observer.observe(element);
|
94
|
+
}
|
95
|
+
};
|
96
|
+
const extendElementWithEagerLoading = element => {
|
97
|
+
if (element.dataset.eager === "true") {
|
98
|
+
if (element.observer) element.observer.disconnect();
|
99
|
+
callWithRetry(dispatchAppearEvent(element));
|
100
|
+
}
|
101
|
+
};
|
102
|
+
class FuturismElement extends HTMLElement {
|
103
|
+
connectedCallback() {
|
104
|
+
extendElementWithIntersectionObserver(this);
|
105
|
+
extendElementWithEagerLoading(this);
|
106
|
+
}
|
107
|
+
}
|
108
|
+
class FuturismTableRow extends HTMLTableRowElement {
|
109
|
+
connectedCallback() {
|
110
|
+
extendElementWithIntersectionObserver(this);
|
111
|
+
extendElementWithEagerLoading(this);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
class FuturismLI extends HTMLLIElement {
|
115
|
+
connectedCallback() {
|
116
|
+
extendElementWithIntersectionObserver(this);
|
117
|
+
extendElementWithEagerLoading(this);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
async function sha256(message) {
|
121
|
+
const msgBuffer = new TextEncoder("utf-8").encode(message);
|
122
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
|
123
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
124
|
+
const hashHex = hashArray.map((b => ("00" + b.toString(16)).slice(-2))).join("");
|
125
|
+
return hashHex;
|
126
|
+
}
|
127
|
+
const polyfillCustomElements = () => {
|
128
|
+
const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
|
129
|
+
if (customElements) {
|
130
|
+
if (isSafari) {
|
131
|
+
document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>');
|
132
|
+
} else {
|
133
|
+
try {
|
134
|
+
customElements.define("built-in", document.createElement("tr").constructor, {
|
135
|
+
extends: "tr"
|
136
|
+
});
|
137
|
+
} catch (_) {
|
138
|
+
document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>');
|
139
|
+
}
|
140
|
+
}
|
141
|
+
} else {
|
142
|
+
document.write('<script src="//unpkg.com/document-register-element"><\/script>');
|
143
|
+
}
|
144
|
+
};
|
145
|
+
const defineElements = e => {
|
146
|
+
if (!customElements.get("futurism-element")) {
|
147
|
+
customElements.define("futurism-element", FuturismElement);
|
148
|
+
customElements.define("futurism-table-row", FuturismTableRow, {
|
149
|
+
extends: "tr"
|
150
|
+
});
|
151
|
+
customElements.define("futurism-li", FuturismLI, {
|
152
|
+
extends: "li"
|
153
|
+
});
|
154
|
+
}
|
155
|
+
};
|
156
|
+
const cachePlaceholders = e => {
|
157
|
+
sha256(e.detail.element.outerHTML).then((hashedContent => {
|
158
|
+
e.detail.element.setAttribute("keep", "");
|
159
|
+
sessionStorage.setItem(`futurism-${hashedContent}`, e.detail.element.outerHTML);
|
160
|
+
e.target.dataset.futurismHash = hashedContent;
|
161
|
+
}));
|
162
|
+
};
|
163
|
+
const restorePlaceholders = e => {
|
164
|
+
const inNamespace = ([key, _payload]) => key.startsWith("futurism-");
|
165
|
+
Object.entries(sessionStorage).filter(inNamespace).forEach((([key, payload]) => {
|
166
|
+
const match = /^futurism-(.*)/.exec(key);
|
167
|
+
const targetElement = document.querySelector(`[data-futurism-hash="${match[1]}"]`);
|
168
|
+
if (targetElement) {
|
169
|
+
targetElement.outerHTML = payload;
|
170
|
+
sessionStorage.removeItem(key);
|
171
|
+
}
|
172
|
+
}));
|
173
|
+
};
|
174
|
+
const initializeElements = () => {
|
175
|
+
polyfillCustomElements();
|
176
|
+
document.addEventListener("DOMContentLoaded", defineElements);
|
177
|
+
document.addEventListener("turbo:load", defineElements);
|
178
|
+
document.addEventListener("turbo:before-cache", restorePlaceholders);
|
179
|
+
document.addEventListener("turbolinks:load", defineElements);
|
180
|
+
document.addEventListener("turbolinks:before-cache", restorePlaceholders);
|
181
|
+
document.addEventListener("cable-ready:after-outer-html", cachePlaceholders);
|
182
|
+
};
|
183
|
+
exports.createSubscription = createSubscription;
|
184
|
+
exports.initializeElements = initializeElements;
|
185
|
+
Object.defineProperty(exports, "__esModule", {
|
186
|
+
value: true
|
187
|
+
});
|
188
|
+
}));
|
@@ -0,0 +1,2 @@
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("cable_ready")):"function"==typeof define&&define.amd?define(["exports","cable_ready"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Futurism={},e.CableReady)}(this,(function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var s=n(t);const r=(e,t=null)=>{if(!window.Futurism)return()=>{setTimeout((()=>r(e,t)()),1)};const n=e.target?e.target:e,s=new CustomEvent("futurism:appear",{bubbles:!0,detail:{target:n,observer:t}});return()=>{n.dispatchEvent(s)}},i=async(e,t=0)=>{try{return await e()}catch(s){if(t>10)throw s;return await(n=1.15**t*2e3,new Promise((e=>setTimeout(e,n)))),i(e,t+1)}var n},a=(e,t)=>{e.forEach((async e=>{e.isIntersecting&&await i(r(e,t))}))},o=e=>{Object.assign(e,{observer:new IntersectionObserver(a.bind(e),{})}),e.hasAttribute("keep")||e.observer.observe(e)},u=e=>{"true"===e.dataset.eager&&(e.observer&&e.observer.disconnect(),i(r(e)))};class c extends HTMLElement{connectedCallback(){o(this),u(this)}}class d extends HTMLTableRowElement{connectedCallback(){o(this),u(this)}}class m extends HTMLLIElement{connectedCallback(){o(this),u(this)}}const l=e=>{customElements.get("futurism-element")||(customElements.define("futurism-element",c),customElements.define("futurism-table-row",d,{extends:"tr"}),customElements.define("futurism-li",m,{extends:"li"}))},f=e=>{(async function(e){const t=new TextEncoder("utf-8").encode(e),n=await crypto.subtle.digest("SHA-256",t);return Array.from(new Uint8Array(n)).map((e=>("00"+e.toString(16)).slice(-2))).join("")})(e.detail.element.outerHTML).then((t=>{e.detail.element.setAttribute("keep",""),sessionStorage.setItem(`futurism-${t}`,e.detail.element.outerHTML),e.target.dataset.futurismHash=t}))},b=e=>{Object.entries(sessionStorage).filter((([e,t])=>e.startsWith("futurism-"))).forEach((([e,t])=>{const n=/^futurism-(.*)/.exec(e),s=document.querySelector(`[data-futurism-hash="${n[1]}"]`);s&&(s.outerHTML=t,sessionStorage.removeItem(e))}))};e.createSubscription=e=>{e.subscriptions.create("Futurism::Channel",{connected(){window.Futurism=this,document.addEventListener("futurism:appear",((e,t=20)=>{let n,s=[];return(...r)=>{clearTimeout(n),s=[...s,...r],n=setTimeout((()=>{n=null,e(s),s=[]}),t)}})((e=>{this.send({signed_params:e.map((e=>e.target.dataset.signedParams)),sgids:e.map((e=>e.target.dataset.sgid)),signed_controllers:e.map((e=>e.target.dataset.signedController)),urls:e.map((e=>window.location.href)),broadcast_each:e.map((e=>e.target.dataset.broadcastEach))})})))},received(e){e.cableReady&&(s.default.perform(e.operations,{emitMissingElementWarnings:!1}),document.dispatchEvent(new CustomEvent("futurism:appeared",{bubbles:!0,cancelable:!0})))}})},e.initializeElements=()=>{(()=>{const e=/^((?!chrome|android).)*safari/i.test(navigator.userAgent);if(customElements)if(e)document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>');else try{customElements.define("built-in",document.createElement("tr").constructor,{extends:"tr"})}catch(e){document.write('<script src="//unpkg.com/@ungap/custom-elements-builtin"><\/script>')}else document.write('<script src="//unpkg.com/document-register-element"><\/script>')})(),document.addEventListener("DOMContentLoaded",l),document.addEventListener("turbo:load",l),document.addEventListener("turbo:before-cache",b),document.addEventListener("turbolinks:load",l),document.addEventListener("turbolinks:before-cache",b),document.addEventListener("cable-ready:after-outer-html",f)},Object.defineProperty(e,"__esModule",{value:!0})}));
|
2
|
+
//# sourceMappingURL=futurism.umd.min.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"futurism.umd.min.js","sources":["../../../javascript/futurism_channel.js","../../../javascript/elements/futurism_utils.js","../../../javascript/elements/futurism_element.js","../../../javascript/elements/futurism_table_row.js","../../../javascript/elements/futurism_li.js","../../../javascript/elements/index.js","../../../javascript/utils/crypto.js"],"sourcesContent":["/* global CustomEvent, setTimeout */\n\nimport CableReady from 'cable_ready'\n\nconst debounceEvents = (callback, delay = 20) => {\n let timeoutId\n let events = []\n return (...args) => {\n clearTimeout(timeoutId)\n events = [...events, ...args]\n timeoutId = setTimeout(() => {\n timeoutId = null\n callback(events)\n events = []\n }, delay)\n }\n}\n\nexport const createSubscription = consumer => {\n consumer.subscriptions.create('Futurism::Channel', {\n connected () {\n window.Futurism = this\n document.addEventListener(\n 'futurism:appear',\n debounceEvents(events => {\n this.send({\n signed_params: events.map(e => e.target.dataset.signedParams),\n sgids: events.map(e => e.target.dataset.sgid),\n signed_controllers: events.map(\n e => e.target.dataset.signedController\n ),\n urls: events.map(_ => window.location.href),\n broadcast_each: events.map(e => e.target.dataset.broadcastEach)\n })\n })\n )\n },\n\n received (data) {\n if (data.cableReady) {\n CableReady.perform(data.operations, {\n emitMissingElementWarnings: false\n })\n\n document.dispatchEvent(\n new CustomEvent('futurism:appeared', {\n bubbles: true,\n cancelable: true\n })\n )\n }\n }\n })\n}\n","/* global IntersectionObserver, CustomEvent, setTimeout */\n\nconst dispatchAppearEvent = (entry, observer = null) => {\n if (!window.Futurism) {\n return () => {\n setTimeout(() => dispatchAppearEvent(entry, observer)(), 1)\n }\n }\n\n const target = entry.target ? entry.target : entry\n\n const evt = new CustomEvent('futurism:appear', {\n bubbles: true,\n detail: {\n target,\n observer\n }\n })\n\n return () => {\n target.dispatchEvent(evt)\n }\n}\n\n// from https://advancedweb.hu/how-to-implement-an-exponential-backoff-retry-strategy-in-javascript/#rejection-based-retrying\nconst wait = ms => new Promise(resolve => setTimeout(resolve, ms))\n\nconst callWithRetry = async (fn, depth = 0) => {\n try {\n return await fn()\n } catch (e) {\n if (depth > 10) {\n throw e\n }\n await wait(1.15 ** depth * 2000)\n\n return callWithRetry(fn, depth + 1)\n }\n}\n\nconst observerCallback = (entries, observer) => {\n entries.forEach(async entry => {\n if (!entry.isIntersecting) return\n\n await callWithRetry(dispatchAppearEvent(entry, observer))\n })\n}\n\nexport const extendElementWithIntersectionObserver = element => {\n Object.assign(element, {\n observer: new IntersectionObserver(observerCallback.bind(element), {})\n })\n\n if (!element.hasAttribute('keep')) {\n element.observer.observe(element)\n }\n}\n\nexport const extendElementWithEagerLoading = element => {\n if (element.dataset.eager === 'true') {\n if (element.observer) element.observer.disconnect()\n callWithRetry(dispatchAppearEvent(element))\n }\n}\n","/* global HTMLElement */\n\nimport {\n extendElementWithIntersectionObserver,\n extendElementWithEagerLoading\n} from './futurism_utils'\n\nexport default class FuturismElement extends HTMLElement {\n connectedCallback () {\n extendElementWithIntersectionObserver(this)\n extendElementWithEagerLoading(this)\n }\n}\n","/* global HTMLTableRowElement */\n\nimport {\n extendElementWithIntersectionObserver,\n extendElementWithEagerLoading\n} from './futurism_utils'\n\nexport default class FuturismTableRow extends HTMLTableRowElement {\n connectedCallback () {\n extendElementWithIntersectionObserver(this)\n extendElementWithEagerLoading(this)\n }\n}\n","/* global HTMLLIElement */\r\n\r\nimport {\r\n extendElementWithIntersectionObserver,\r\n extendElementWithEagerLoading\r\n} from './futurism_utils'\r\n\r\nexport default class FuturismLI extends HTMLLIElement {\r\n connectedCallback () {\r\n extendElementWithIntersectionObserver(this)\r\n extendElementWithEagerLoading(this)\r\n }\r\n}\r\n","/* global customElements, sessionStorage */\n\nimport FuturismElement from './futurism_element'\nimport FuturismTableRow from './futurism_table_row'\nimport FuturismLI from './futurism_li'\n\nimport { sha256 } from '../utils/crypto'\n\nconst polyfillCustomElements = () => {\n const isSafari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent)\n\n if (customElements) {\n if (isSafari) {\n document.write(\n '<script src=\"//unpkg.com/@ungap/custom-elements-builtin\"><\\x2fscript>'\n )\n } else {\n try {\n customElements.define(\n 'built-in',\n document.createElement('tr').constructor,\n { extends: 'tr' }\n )\n } catch (_) {\n document.write(\n '<script src=\"//unpkg.com/@ungap/custom-elements-builtin\"><\\x2fscript>'\n )\n }\n }\n } else {\n document.write(\n '<script src=\"//unpkg.com/document-register-element\"><\\x2fscript>'\n )\n }\n}\n\nconst defineElements = e => {\n if (!customElements.get('futurism-element')) {\n customElements.define('futurism-element', FuturismElement)\n customElements.define('futurism-table-row', FuturismTableRow, {\n extends: 'tr'\n })\n customElements.define('futurism-li', FuturismLI, { extends: 'li' })\n }\n}\n\nconst cachePlaceholders = e => {\n sha256(e.detail.element.outerHTML).then(hashedContent => {\n e.detail.element.setAttribute('keep', '')\n sessionStorage.setItem(\n `futurism-${hashedContent}`,\n e.detail.element.outerHTML\n )\n e.target.dataset.futurismHash = hashedContent\n })\n}\n\nconst restorePlaceholders = e => {\n const inNamespace = ([key, _payload]) => key.startsWith('futurism-')\n Object.entries(sessionStorage)\n .filter(inNamespace)\n .forEach(([key, payload]) => {\n const match = /^futurism-(.*)/.exec(key)\n const targetElement = document.querySelector(\n `[data-futurism-hash=\"${match[1]}\"]`\n )\n\n if (targetElement) {\n targetElement.outerHTML = payload\n sessionStorage.removeItem(key)\n }\n })\n}\n\nexport const initializeElements = () => {\n polyfillCustomElements()\n document.addEventListener('DOMContentLoaded', defineElements)\n document.addEventListener('turbo:load', defineElements)\n document.addEventListener('turbo:before-cache', restorePlaceholders)\n document.addEventListener('turbolinks:load', defineElements)\n document.addEventListener('turbolinks:before-cache', restorePlaceholders)\n document.addEventListener('cable-ready:after-outer-html', cachePlaceholders)\n}\n","/* global crypto */\n\nexport async function sha256 (message) {\n // encode as UTF-8\n const msgBuffer = new TextEncoder('utf-8').encode(message)\n\n // hash the message\n const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer)\n\n // convert ArrayBuffer to Array\n const hashArray = Array.from(new Uint8Array(hashBuffer))\n\n // convert bytes to hex string\n const hashHex = hashArray.map(b => ('00' + b.toString(16)).slice(-2)).join('')\n\n return hashHex\n}\n"],"names":["dispatchAppearEvent","entry","observer","window","Futurism","setTimeout","target","evt","CustomEvent","bubbles","detail","dispatchEvent","callWithRetry","async","fn","depth","e","ms","Promise","resolve","observerCallback","entries","forEach","isIntersecting","extendElementWithIntersectionObserver","element","Object","assign","IntersectionObserver","bind","hasAttribute","observe","extendElementWithEagerLoading","dataset","eager","disconnect","FuturismElement","HTMLElement","connectedCallback","this","FuturismTableRow","HTMLTableRowElement","FuturismLI","HTMLLIElement","defineElements","customElements","get","define","extends","cachePlaceholders","message","msgBuffer","TextEncoder","encode","hashBuffer","crypto","subtle","digest","Array","from","Uint8Array","map","b","toString","slice","join","sha256","outerHTML","then","hashedContent","setAttribute","sessionStorage","setItem","futurismHash","restorePlaceholders","filter","key","_payload","startsWith","payload","match","exec","targetElement","document","querySelector","removeItem","consumer","subscriptions","create","connected","addEventListener","callback","delay","timeoutId","events","args","clearTimeout","debounceEvents","send","signed_params","signedParams","sgids","sgid","signed_controllers","signedController","urls","_","location","href","broadcast_each","broadcastEach","received","data","cableReady","CableReady","perform","operations","emitMissingElementWarnings","cancelable","isSafari","test","navigator","userAgent","write","createElement","constructor","polyfillCustomElements"],"mappings":"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"}
|
data/bin/rails
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails gems
|
3
|
+
# installed from the root of your application.
|
4
|
+
|
5
|
+
ENGINE_ROOT = File.expand_path('..', __dir__)
|
6
|
+
ENGINE_PATH = File.expand_path('../lib/futurism/engine', __dir__)
|
7
|
+
APP_PATH = File.expand_path('../test/dummy/config/application', __dir__)
|
8
|
+
|
9
|
+
# Set up gems listed in the Gemfile.
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
|
11
|
+
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
|
12
|
+
|
13
|
+
require "rails"
|
14
|
+
# Pick the frameworks you want:
|
15
|
+
require "active_model/railtie"
|
16
|
+
require "active_job/railtie"
|
17
|
+
# require "active_record/railtie"
|
18
|
+
# require "active_storage/engine"
|
19
|
+
require "action_controller/railtie"
|
20
|
+
# require "action_mailer/railtie"
|
21
|
+
require "action_view/railtie"
|
22
|
+
require "action_cable/engine"
|
23
|
+
# require "sprockets/railtie"
|
24
|
+
require "rails/test_unit/railtie"
|
25
|
+
require 'rails/engine/commands'
|
data/bin/standardize
ADDED
data/bin/test
ADDED
data/futurism.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
$:.push File.expand_path("lib", __dir__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "futurism/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "futurism"
|
9
|
+
spec.version = Futurism::VERSION
|
10
|
+
spec.authors = ["Julian Rubisch"]
|
11
|
+
spec.email = ["julian@julianrubisch.at"]
|
12
|
+
spec.homepage = "https://github.com/stimulusreflex/futurism"
|
13
|
+
spec.summary = "Lazy-load Rails partials via CableReady"
|
14
|
+
spec.description = "Uses custom html elements with attached IntersectionObserver to automatically lazy load partials via websockets"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = Dir[
|
18
|
+
"lib/**/*.rb",
|
19
|
+
"app/**/*.rb",
|
20
|
+
"app/assets/javascripts/*",
|
21
|
+
"bin/*",
|
22
|
+
"[A-Z]*"
|
23
|
+
]
|
24
|
+
|
25
|
+
spec.test_files = Dir["test/**/*.rb"]
|
26
|
+
|
27
|
+
spec.add_development_dependency "appraisal"
|
28
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
29
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
30
|
+
spec.add_development_dependency "pry", "~> 0.12.2"
|
31
|
+
spec.add_development_dependency "nokogiri"
|
32
|
+
spec.add_development_dependency "standardrb"
|
33
|
+
spec.add_development_dependency "sqlite3"
|
34
|
+
|
35
|
+
spec.add_dependency "rack", "~> 2.0"
|
36
|
+
spec.add_dependency "rails", ">= 5.2"
|
37
|
+
spec.add_dependency "cable_ready", "= 5.0.0.pre9"
|
38
|
+
end
|
data/futurism.gemspec~
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
$:.push File.expand_path("lib", __dir__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "futurism/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = "futurism"
|
9
|
+
spec.version = Futurism::VERSION
|
10
|
+
spec.authors = ["Julian Rubisch"]
|
11
|
+
spec.email = ["julian@julianrubisch.at"]
|
12
|
+
spec.homepage = "https://github.com/stimulusreflex/futurism"
|
13
|
+
spec.summary = "Lazy-load Rails partials via CableReady"
|
14
|
+
spec.description = "Uses custom html elements with attached IntersectionObserver to automatically lazy load partials via websockets"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.md"]
|
18
|
+
|
19
|
+
spec.add_development_dependency "appraisal"
|
20
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
21
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
22
|
+
spec.add_development_dependency "pry", "~> 0.12.2"
|
23
|
+
spec.add_development_dependency "nokogiri"
|
24
|
+
spec.add_development_dependency "standardrb"
|
25
|
+
spec.add_development_dependency "sqlite3"
|
26
|
+
|
27
|
+
spec.add_dependency "rack", "~> 2.0"
|
28
|
+
spec.add_dependency "rails", ">= 5.2"
|
29
|
+
spec.add_dependency "cable_ready", "= 5.0.0.pre8"
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Futurism
|
2
|
+
class << self
|
3
|
+
def configure
|
4
|
+
yield configuration
|
5
|
+
end
|
6
|
+
|
7
|
+
def configuration
|
8
|
+
@configuration ||= Configuration.new
|
9
|
+
end
|
10
|
+
|
11
|
+
alias_method :config, :configuration
|
12
|
+
end
|
13
|
+
|
14
|
+
class Configuration
|
15
|
+
attr_accessor :parent_channel
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@parent_channel = "::ApplicationCable::Channel"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/futurism/engine.rb
CHANGED
@@ -1,4 +1,23 @@
|
|
1
1
|
module Futurism
|
2
2
|
class Engine < ::Rails::Engine
|
3
|
+
initializer "futurism.assets" do |app|
|
4
|
+
if app.config.respond_to?(:assets)
|
5
|
+
app.config.assets.precompile += %w[
|
6
|
+
futurism.js
|
7
|
+
futurism.min.js
|
8
|
+
futurism.min.js.map
|
9
|
+
futurism.umd.js
|
10
|
+
futurism.umd.min.js
|
11
|
+
futurism.umd.min.js.map
|
12
|
+
]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
initializer "futurism.importmap", before: "importmap" do |app|
|
17
|
+
if app.config.respond_to?(:importmap)
|
18
|
+
app.config.importmap.paths << Engine.root.join("lib/futurism/importmap.rb")
|
19
|
+
app.config.importmap.cache_sweepers << Engine.root.join("app/assets/javascripts")
|
20
|
+
end
|
21
|
+
end
|
3
22
|
end
|
4
23
|
end
|
data/lib/futurism/helpers.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Futurism
|
2
2
|
module Helpers
|
3
|
-
def futurize(records_or_string = nil, extends
|
3
|
+
def futurize(records_or_string = nil, extends: :div, **options, &block)
|
4
4
|
if (Rails.env.test? && Futurism.skip_in_test) || options[:unless]
|
5
5
|
if records_or_string.nil?
|
6
6
|
return render(**options)
|
@@ -77,7 +77,7 @@ module Futurism
|
|
77
77
|
def dataset
|
78
78
|
data_attributes.merge({
|
79
79
|
signed_params: signed_params,
|
80
|
-
sgid: model && model.to_sgid.to_s,
|
80
|
+
sgid: model && model.to_sgid(expires_in: nil).to_s,
|
81
81
|
eager: eager.presence,
|
82
82
|
broadcast_each: broadcast_each.presence,
|
83
83
|
signed_controller: signed_controller
|
data/lib/futurism/version.rb
CHANGED