futurism 1.2.0.pre7 → 1.2.0.pre10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +24 -0
  3. data/Appraisals~ +24 -0
  4. data/CHANGELOG.md +370 -0
  5. data/Gemfile +15 -0
  6. data/Gemfile.lock +215 -0
  7. data/Gemfile~ +17 -0
  8. data/README.md +59 -10
  9. data/README.md~ +405 -0
  10. data/app/assets/javascripts/futurism.js +191 -0
  11. data/app/assets/javascripts/futurism.min.js +2 -0
  12. data/app/assets/javascripts/futurism.min.js.map +1 -0
  13. data/app/assets/javascripts/futurism.umd.js +188 -0
  14. data/app/assets/javascripts/futurism.umd.min.js +2 -0
  15. data/app/assets/javascripts/futurism.umd.min.js.map +1 -0
  16. data/{lib → app/channels}/futurism/channel.rb +1 -1
  17. data/bin/rails +25 -0
  18. data/bin/standardize +5 -0
  19. data/bin/test +5 -0
  20. data/futurism.gemspec +38 -0
  21. data/futurism.gemspec~ +30 -0
  22. data/lib/futurism/configuration.rb +21 -0
  23. data/lib/futurism/engine.rb +19 -0
  24. data/lib/futurism/helpers.rb +5 -2
  25. data/lib/futurism/importmap.rb +2 -0
  26. data/lib/futurism/version.rb +1 -1
  27. data/lib/futurism.rb +2 -1
  28. data/package.json +47 -0
  29. data/package.json~ +51 -0
  30. data/rollup.config.js +77 -0
  31. data/rollup.config.js~ +73 -0
  32. data/test/cable/channel_test.rb +319 -0
  33. data/test/dummy/app/channels/application_cable/channel.rb +4 -0
  34. data/test/dummy/app/channels/application_cable/connection.rb +4 -0
  35. data/test/dummy/app/controllers/application_controller.rb +2 -0
  36. data/test/dummy/app/controllers/home_controller.rb +6 -0
  37. data/test/dummy/app/controllers/posts_controller.rb +59 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/helpers/posts_helper.rb +2 -0
  40. data/test/dummy/app/jobs/application_job.rb +7 -0
  41. data/test/dummy/app/models/action_item.rb +2 -0
  42. data/test/dummy/app/models/application_record.rb +3 -0
  43. data/test/dummy/app/models/post.rb +2 -0
  44. data/test/dummy/config/application.rb +29 -0
  45. data/test/dummy/config/boot.rb +5 -0
  46. data/test/dummy/config/environment.rb +5 -0
  47. data/test/dummy/config/environments/development.rb +40 -0
  48. data/test/dummy/config/environments/production.rb +94 -0
  49. data/test/dummy/config/environments/test.rb +39 -0
  50. data/test/dummy/config/initializers/application_controller_renderer.rb +8 -0
  51. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  52. data/test/dummy/config/initializers/content_security_policy.rb +28 -0
  53. data/test/dummy/config/initializers/cookies_serializer.rb +5 -0
  54. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  55. data/test/dummy/config/initializers/inflections.rb +16 -0
  56. data/test/dummy/config/initializers/mime_types.rb +4 -0
  57. data/test/dummy/config/initializers/wrap_parameters.rb +9 -0
  58. data/test/dummy/config/puma.rb +38 -0
  59. data/test/dummy/config/routes.rb +12 -0
  60. data/test/dummy/config/spring.rb +6 -0
  61. data/test/dummy/db/migrate/20200711122838_create_posts.rb +9 -0
  62. data/test/dummy/db/migrate/2021042923813_create_action_items.rb +9 -0
  63. data/test/dummy/db/schema.rb +27 -0
  64. data/test/futurism_test.rb +28 -0
  65. data/test/helper/helper_test.rb +206 -0
  66. data/test/integration/navigation_test.rb +7 -0
  67. data/test/resolver/controller/renderer_test.rb +120 -0
  68. data/test/resolver/controller_test.rb +26 -0
  69. data/test/test_helper.rb +14 -0
  70. data/yarn.lock +3263 -0
  71. metadata +122 -18
  72. data/config/routes.rb +0 -2
  73. data/lib/futurism/channel.rb~ +0 -31
  74. data/lib/futurism/helpers.rb~ +0 -118
  75. data/lib/futurism/options_transformer.rb~ +0 -0
  76. data/lib/futurism/resolver/controller/renderer.rb~ +0 -76
  77. data/lib/futurism/resolver/controller.rb~ +0 -17
  78. data/lib/futurism/resolver/resources.rb~ +0 -101
  79. data/lib/futurism/version.rb~ +0 -3
  80. data/lib/futurism.rb~ +0 -30
  81. data/lib/tasks/futurism_tasks.rake +0 -39
  82. 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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"}
@@ -1,5 +1,5 @@
1
1
  module Futurism
2
- class Channel < ActionCable::Channel::Base
2
+ class Channel < Futurism.configuration.parent_channel.constantize
3
3
  include CableReady::Broadcaster
4
4
 
5
5
  def stream_name
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
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+
3
+ bundle exec standardrb --fix
4
+
5
+ npx prettier-standard lib/templates/**/*.js
data/bin/test ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+ $: << File.expand_path("../test", __dir__)
3
+
4
+ require "bundler/setup"
5
+ require "rails/plugin/test"
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
@@ -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
@@ -1,7 +1,7 @@
1
1
  module Futurism
2
2
  module Helpers
3
- def futurize(records_or_string = nil, extends:, **options, &block)
4
- if Rails.env.test? && Futurism.skip_in_test
3
+ def futurize(records_or_string = nil, extends: :div, **options, &block)
4
+ if (Rails.env.test? && Futurism.skip_in_test) || options[:unless]
5
5
  if records_or_string.nil?
6
6
  return render(**options)
7
7
  else
@@ -11,6 +11,9 @@ module Futurism
11
11
 
12
12
  options[:eager] = true unless block_given?
13
13
 
14
+ # cannot serialize a proc
15
+ options.delete(:cached) if options[:cached].is_a?(Proc)
16
+
14
17
  if records_or_string.is_a?(ActiveRecord::Base) || records_or_string.is_a?(ActiveRecord::Relation)
15
18
  futurize_active_record(records_or_string, extends: extends, **options, &block)
16
19
  elsif records_or_string.is_a?(String)
@@ -0,0 +1,2 @@
1
+ pin "cable_ready", to: "cable_ready.min.js", preload: true
2
+ pin "futurism", to: "futurism.min.js", preload: true
@@ -1,3 +1,3 @@
1
1
  module Futurism
2
- VERSION = "1.2.0.pre7"
2
+ VERSION = "1.2.0.pre10"
3
3
  end