futurism 1.2.0.pre9 → 1.2.0.pre10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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 +1 -1
- 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)
|
data/lib/futurism/version.rb
CHANGED