tina4ruby 3.13.111 → 3.13.112
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/lib/tina4/dev_admin.rb +10 -0
- data/lib/tina4/rack_app.rb +186 -169
- data/lib/tina4/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0c0ba91f4e37ef96e557eb291a221cf419821ad59a00bad3e4fe1dbbfaaa5a97
|
|
4
|
+
data.tar.gz: c54f721f615114967ed5f4e6e86b1c3573da6fb85dcf4fcc9003f0840608b848
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ada54d7fac14bbfec973040294322d70155c99024c071a4d5a9b558f0ab16d69bf2c57020e92db43ca70a1eeb00099944ddf6d6cf40268ba51f755621426e5c
|
|
7
|
+
data.tar.gz: b95811c10bf0db5264f304e19ade1f6849f790c4eec98c4106ed2c7a4201380492082fc4a67ef32fd517e0ad58997a7c3888d49e37e79c845e2d2c92c6fa40d4
|
data/lib/tina4/dev_admin.rb
CHANGED
|
@@ -407,6 +407,16 @@ module Tina4
|
|
|
407
407
|
serve_dashboard
|
|
408
408
|
when ["GET", "/__dev/js/tina4-dev-admin.min.js"]
|
|
409
409
|
serve_dev_js
|
|
410
|
+
when ["GET", "/__dev/toolbar.css"]
|
|
411
|
+
# tina4stack #115: the injected toolbar's stylesheet, served as an
|
|
412
|
+
# external asset so the toolbar carries no inline style= and renders
|
|
413
|
+
# under the default default-src 'self' CSP. Parity with PHP/Python/Node.
|
|
414
|
+
[200, { "content-type" => "text/css; charset=utf-8" }, [Tina4::RackApp.toolbar_css]]
|
|
415
|
+
when ["GET", "/__dev/toolbar.js"]
|
|
416
|
+
# tina4stack #115: the injected toolbar's script (version modal, dash
|
|
417
|
+
# overlay, WebSocket-primary reloader), served as an external asset so
|
|
418
|
+
# the toolbar carries no inline <script>/onclick and stays CSP-clean.
|
|
419
|
+
[200, { "content-type" => "application/javascript; charset=utf-8" }, [Tina4::RackApp.toolbar_js]]
|
|
410
420
|
when ["GET", "/__dev/api/mtime"]
|
|
411
421
|
json_response({ mtime: @reload_mtime || 0, file: @reload_file || "" })
|
|
412
422
|
when ["POST", "/__dev/api/reload"]
|
data/lib/tina4/rack_app.rb
CHANGED
|
@@ -915,64 +915,174 @@ module Tina4
|
|
|
915
915
|
[-1, {}, []]
|
|
916
916
|
end
|
|
917
917
|
|
|
918
|
+
# Inject the CSP-clean dev toolbar into HTML responses in dev mode.
|
|
919
|
+
#
|
|
920
|
+
# tina4stack #115: the toolbar is CSP-clean - it carries NO inline `style=`,
|
|
921
|
+
# NO `onclick=` (or any inline handler) and NO inline `<script>` block. All
|
|
922
|
+
# styling lives in the external `/__dev/toolbar.css` stylesheet and every
|
|
923
|
+
# interaction is wired via `addEventListener` in the external
|
|
924
|
+
# `/__dev/toolbar.js`, so the toolbar renders under the framework's default
|
|
925
|
+
# `default-src 'self'` CSP with no violations. Parity with PHP/Python/Node,
|
|
926
|
+
# which serve the same external `/__dev/toolbar.{css,js}` assets.
|
|
918
927
|
def inject_dev_overlay(body, request_info, ai_port: false)
|
|
919
928
|
version = Tina4::VERSION
|
|
920
|
-
# DEVADMIN-DEC-04 (feature 127): the toolbar
|
|
921
|
-
#
|
|
922
|
-
#
|
|
923
|
-
#
|
|
924
|
-
#
|
|
925
|
-
# and the Python master html.escape; Ruby was reflecting them raw.)
|
|
929
|
+
# DEVADMIN-DEC-04 (feature 127): the toolbar rides EVERY text/html response
|
|
930
|
+
# (including 404s), so the reflected request method/path/matched-pattern
|
|
931
|
+
# MUST be HTML-escaped or a crafted path reflects <script> that runs in the
|
|
932
|
+
# dev-server origin and can then drive every ungated /__dev mutation route.
|
|
933
|
+
# (Parity with PHP htmlspecialchars and the Python master html.escape.)
|
|
926
934
|
method = CGI.escapeHTML(request_info[:method].to_s)
|
|
927
935
|
path = CGI.escapeHTML(request_info[:path].to_s)
|
|
928
936
|
matched_pattern = CGI.escapeHTML(request_info[:matched_pattern].to_s)
|
|
929
|
-
request_id = Tina4::Log.get_request_id || "-"
|
|
937
|
+
request_id = CGI.escapeHTML((Tina4::Log.get_request_id || "-").to_s)
|
|
930
938
|
route_count = Tina4::Router.routes.length
|
|
939
|
+
ruby_version = RUBY_VERSION
|
|
931
940
|
|
|
932
|
-
|
|
941
|
+
# data-reload gates the live reloader in toolbar.js: "1" on the human dev
|
|
942
|
+
# port, "0" on the AI/stable port (the JS early-returns before starting the
|
|
943
|
+
# reloader when it is not "1"). Matches Python's render_dev_toolbar.
|
|
944
|
+
reload = ai_port ? "0" : "1"
|
|
945
|
+
ai_badge = ai_port ? '<span class="t4-ai-badge">AI PORT</span>' : ""
|
|
933
946
|
|
|
934
947
|
toolbar = <<~HTML.strip
|
|
935
|
-
<
|
|
936
|
-
|
|
937
|
-
<
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
<
|
|
948
|
+
<link rel="stylesheet" href="/__dev/toolbar.css">
|
|
949
|
+
<div id="tina4-dev-toolbar" data-reload="#{reload}">
|
|
950
|
+
#{ai_badge}<span id="tina4-ver-btn" title="Click to check for updates">Tina4 v#{version}</span>
|
|
951
|
+
<div id="tina4-ver-modal">
|
|
952
|
+
<div class="t4-modal-head">
|
|
953
|
+
<strong class="t4-modal-title">Version Info</strong>
|
|
954
|
+
<span id="tina4-ver-close" class="t4-x">×</span>
|
|
941
955
|
</div>
|
|
942
|
-
<div id="tina4-ver-body"
|
|
943
|
-
<div>Current: <strong
|
|
944
|
-
<div id="tina4-ver-latest"
|
|
956
|
+
<div id="tina4-ver-body">
|
|
957
|
+
<div>Current: <strong class="t4-ok">v#{version}</strong></div>
|
|
958
|
+
<div id="tina4-ver-latest" class="t4-dim">Checking for updates...</div>
|
|
945
959
|
</div>
|
|
946
960
|
</div>
|
|
947
|
-
<span
|
|
961
|
+
<span class="t4-green">#{method}</span>
|
|
948
962
|
<span>#{path}</span>
|
|
949
|
-
<span
|
|
950
|
-
<span
|
|
951
|
-
<span
|
|
952
|
-
<span
|
|
953
|
-
<a href="#"
|
|
954
|
-
<span
|
|
963
|
+
<span class="t4-arrow">→ #{matched_pattern}</span>
|
|
964
|
+
<span class="t4-yellow">req:#{request_id}</span>
|
|
965
|
+
<span class="t4-blue">#{route_count} routes</span>
|
|
966
|
+
<span class="t4-dim">Ruby #{ruby_version}</span>
|
|
967
|
+
<a href="#" id="tina4-dash-link" class="t4-dash">Dashboard ↗</a>
|
|
968
|
+
<span id="tina4-bar-close" class="t4-x t4-bar-close">✕</span>
|
|
955
969
|
</div>
|
|
956
|
-
<script>
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
970
|
+
<script src="/__dev/toolbar.js"></script>
|
|
971
|
+
HTML
|
|
972
|
+
|
|
973
|
+
if body.include?("</body>")
|
|
974
|
+
body.sub("</body>", "#{toolbar}\n</body>")
|
|
975
|
+
else
|
|
976
|
+
body + "\n" + toolbar
|
|
977
|
+
end
|
|
978
|
+
end
|
|
979
|
+
|
|
980
|
+
# CSS for the injected dev toolbar. Served as an external stylesheet (see
|
|
981
|
+
# DevAdmin.handle_request) so the toolbar carries no inline `style=` and
|
|
982
|
+
# stays CSP-clean under a strict `default-src 'self'`.
|
|
983
|
+
def self.toolbar_css
|
|
984
|
+
<<~CSS
|
|
985
|
+
#tina4-dev-toolbar{position:fixed;bottom:0;left:0;right:0;background:#333;color:#fff;font-family:monospace;font-size:12px;padding:6px 16px;z-index:99999;display:flex;align-items:center;gap:16px}
|
|
986
|
+
#tina4-dev-toolbar a{text-decoration:none}
|
|
987
|
+
.t4-ai-badge{background:#7c3aed;color:#fff;font-size:10px;padding:1px 6px;border-radius:3px;font-weight:bold}
|
|
988
|
+
#tina4-ver-btn{color:#d32f2f;font-weight:bold;cursor:pointer;text-decoration:underline dotted}
|
|
989
|
+
#tina4-ver-modal{display:none;position:fixed;bottom:3rem;left:1rem;background:#1e1e2e;border:1px solid #d32f2f;border-radius:8px;padding:16px 20px;z-index:100000;min-width:320px;box-shadow:0 8px 32px rgba(0,0,0,.5);font-family:monospace;font-size:13px;color:#cdd6f4}
|
|
990
|
+
.t4-modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:12px}
|
|
991
|
+
.t4-modal-title{color:#89b4fa}
|
|
992
|
+
#tina4-ver-body{line-height:1.8}
|
|
993
|
+
.t4-x{cursor:pointer;color:#888}
|
|
994
|
+
.t4-bar-close{margin-left:8px}
|
|
995
|
+
.t4-green{color:#4caf50}
|
|
996
|
+
.t4-dim{color:#888}
|
|
997
|
+
.t4-arrow{color:#666}
|
|
998
|
+
.t4-yellow{color:#ffeb3b}
|
|
999
|
+
.t4-blue{color:#90caf9}
|
|
1000
|
+
.t4-ok{color:#a6e3a1}
|
|
1001
|
+
.t4-warn{color:#f9e2af}
|
|
1002
|
+
.t4-err{color:#f38ba8}
|
|
1003
|
+
.t4-purple{color:#cba6f7}
|
|
1004
|
+
.t4-link{color:#89b4fa}
|
|
1005
|
+
.t4-code{background:#313244;padding:2px 6px;border-radius:3px}
|
|
1006
|
+
.t4-note{margin-top:6px}
|
|
1007
|
+
.t4-dash{color:#ef9a9a;margin-left:auto;cursor:pointer}
|
|
1008
|
+
#tina4-dev-panel{position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all .2s}
|
|
1009
|
+
#tina4-dev-panel iframe{width:100%;height:100%;border:1px solid #CC342D;border-radius:.5rem;box-shadow:0 8px 32px rgba(0,0,0,.5);background:#0f172a}
|
|
1010
|
+
CSS
|
|
1011
|
+
end
|
|
1012
|
+
|
|
1013
|
+
# JS for the injected dev toolbar - the version-check modal, the dashboard
|
|
1014
|
+
# overlay, and the WebSocket-primary live reloader. Served as an external
|
|
1015
|
+
# script (see DevAdmin.handle_request) so the toolbar carries no inline
|
|
1016
|
+
# handlers or `<script>` and stays CSP-clean. Every handler is wired via
|
|
1017
|
+
# addEventListener; the reloader only starts when the toolbar's `data-reload`
|
|
1018
|
+
# attribute is "1" (reload not suppressed for this request). Mirrors the
|
|
1019
|
+
# Python master's injected client and the PHP external toolbar.js exactly.
|
|
1020
|
+
def self.toolbar_js
|
|
1021
|
+
poll_interval_ms = (ENV["TINA4_DEV_POLL_INTERVAL"] || "3000").to_i
|
|
1022
|
+
poll_interval_ms = 3000 if poll_interval_ms <= 0
|
|
1023
|
+
<<~JS
|
|
1024
|
+
(function () {
|
|
1025
|
+
var bar = document.getElementById('tina4-dev-toolbar');
|
|
1026
|
+
if (!bar) { return; }
|
|
1027
|
+
|
|
1028
|
+
var modal = document.getElementById('tina4-ver-modal');
|
|
1029
|
+
function upToDate(el, latest) {
|
|
1030
|
+
el.className = 't4-ok';
|
|
1031
|
+
el.innerHTML = 'Latest: <strong class="t4-ok">v' + latest + '</strong> — You are up to date!';
|
|
1032
|
+
}
|
|
1033
|
+
function checkVersion() {
|
|
1034
|
+
if (modal.style.display === 'block') { modal.style.display = 'none'; return; }
|
|
1035
|
+
modal.style.display = 'block';
|
|
1036
|
+
var el = document.getElementById('tina4-ver-latest');
|
|
1037
|
+
el.className = 't4-dim';
|
|
1038
|
+
el.textContent = 'Checking for updates...';
|
|
1039
|
+
fetch('/__dev/api/version-check').then(function (r) { return r.json(); }).then(function (d) {
|
|
1040
|
+
var latest = d.latest, current = d.current;
|
|
1041
|
+
if (latest === current) { upToDate(el, latest); return; }
|
|
1042
|
+
var cP = current.split('.').map(Number), lP = latest.split('.').map(Number);
|
|
1043
|
+
var isNewer = false, i, c, l;
|
|
1044
|
+
for (i = 0; i < Math.max(cP.length, lP.length); i++) { c = cP[i] || 0; l = lP[i] || 0; if (l > c) { isNewer = true; break; } if (l < c) { break; } }
|
|
1045
|
+
var isAhead = false;
|
|
1046
|
+
if (!isNewer) { for (i = 0; i < Math.max(cP.length, lP.length); i++) { var c2 = cP[i] || 0, l2 = lP[i] || 0; if (c2 > l2) { isAhead = true; break; } if (c2 < l2) { break; } } }
|
|
1047
|
+
if (isNewer) {
|
|
1048
|
+
var breaking = (lP[0] !== cP[0] || lP[1] !== cP[1]);
|
|
1049
|
+
el.className = '';
|
|
1050
|
+
el.innerHTML = 'Latest: <strong class="t4-warn">v' + latest + '</strong>';
|
|
1051
|
+
if (breaking) {
|
|
1052
|
+
el.innerHTML += '<div class="t4-err t4-note">⚠ Major/minor version change — check the <a href="https://github.com/tina4stack/tina4-ruby/releases" target="_blank" class="t4-link">changelog</a> for breaking changes before upgrading.</div>';
|
|
1053
|
+
} else {
|
|
1054
|
+
el.innerHTML += '<div class="t4-warn t4-note">Patch update available. Run: <code class="t4-code">gem install tina4ruby</code></div>';
|
|
1055
|
+
}
|
|
1056
|
+
} else if (isAhead) {
|
|
1057
|
+
el.className = 't4-purple';
|
|
1058
|
+
el.innerHTML = 'You are running <strong class="t4-purple">v' + current + '</strong> (ahead of RubyGems <strong>v' + latest + '</strong> — not yet published).';
|
|
1059
|
+
} else {
|
|
1060
|
+
upToDate(el, latest);
|
|
1061
|
+
}
|
|
1062
|
+
}).catch(function () {
|
|
1063
|
+
el.className = 't4-err';
|
|
1064
|
+
el.textContent = 'Could not check for updates (offline?)';
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
var verBtn = document.getElementById('tina4-ver-btn');
|
|
1068
|
+
if (verBtn) { verBtn.addEventListener('click', checkVersion); }
|
|
1069
|
+
var verClose = document.getElementById('tina4-ver-close');
|
|
1070
|
+
if (verClose) { verClose.addEventListener('click', function () { modal.style.display = 'none'; }); }
|
|
1071
|
+
var barClose = document.getElementById('tina4-bar-close');
|
|
1072
|
+
if (barClose) { barClose.addEventListener('click', function () { bar.style.display = 'none'; }); }
|
|
1073
|
+
|
|
962
1074
|
var STATE_KEY = 'tina4_dev_overlay_open';
|
|
963
1075
|
function buildOverlay() {
|
|
964
1076
|
var c = document.createElement('div');
|
|
965
1077
|
c.id = 'tina4-dev-panel';
|
|
966
|
-
c.style.cssText = 'position:fixed;top:3rem;left:0;right:0;bottom:2rem;z-index:99998;transition:all 0.2s';
|
|
967
1078
|
var f = document.createElement('iframe');
|
|
968
1079
|
f.src = '/__dev';
|
|
969
|
-
f.style.cssText = 'width:100%;height:100%;border:1px solid #CC342D;border-radius:0.5rem;box-shadow:0 8px 32px rgba(0,0,0,0.5);background:#0f172a';
|
|
970
1080
|
c.appendChild(f);
|
|
971
1081
|
document.body.appendChild(c);
|
|
972
1082
|
return c;
|
|
973
1083
|
}
|
|
974
|
-
|
|
975
|
-
if (e) e.preventDefault();
|
|
1084
|
+
function toggleOverlay(e) {
|
|
1085
|
+
if (e) { e.preventDefault(); }
|
|
976
1086
|
var p = document.getElementById('tina4-dev-panel');
|
|
977
1087
|
if (p) {
|
|
978
1088
|
var hide = p.style.display !== 'none';
|
|
@@ -982,151 +1092,58 @@ module Tina4
|
|
|
982
1092
|
}
|
|
983
1093
|
buildOverlay();
|
|
984
1094
|
try { localStorage.setItem(STATE_KEY, '1'); } catch (_) {}
|
|
985
|
-
};
|
|
986
|
-
function restoreIfOpen() {
|
|
987
|
-
try {
|
|
988
|
-
if (location.pathname.indexOf('/__dev') === 0) return;
|
|
989
|
-
if (localStorage.getItem(STATE_KEY) === '1' && !document.getElementById('tina4-dev-panel')) {
|
|
990
|
-
buildOverlay();
|
|
991
|
-
}
|
|
992
|
-
} catch (_) {}
|
|
993
|
-
}
|
|
994
|
-
if (document.readyState === 'loading') {
|
|
995
|
-
document.addEventListener('DOMContentLoaded', restoreIfOpen);
|
|
996
|
-
} else {
|
|
997
|
-
restoreIfOpen();
|
|
998
1095
|
}
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
var el=document.getElementById('tina4-ver-latest');
|
|
1007
|
-
el.innerHTML='Checking for updates...';
|
|
1008
|
-
el.style.color='#888';
|
|
1009
|
-
fetch('/__dev/api/version-check')
|
|
1010
|
-
.then(function(r){return r.json()})
|
|
1011
|
-
.then(function(d){
|
|
1012
|
-
var latest=d.latest;
|
|
1013
|
-
var current=d.current;
|
|
1014
|
-
if(latest===current){
|
|
1015
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
1016
|
-
el.style.color='#a6e3a1';
|
|
1017
|
-
}else{
|
|
1018
|
-
var cParts=current.split('.').map(Number);
|
|
1019
|
-
var lParts=latest.split('.').map(Number);
|
|
1020
|
-
var isNewer=false;
|
|
1021
|
-
for(var i=0;i<Math.max(cParts.length,lParts.length);i++){
|
|
1022
|
-
var c=cParts[i]||0,l=lParts[i]||0;
|
|
1023
|
-
if(l>c){isNewer=true;break;}
|
|
1024
|
-
if(l<c)break;
|
|
1025
|
-
}
|
|
1026
|
-
var isAhead=false;
|
|
1027
|
-
if(!isNewer){for(var i=0;i<Math.max(cParts.length,lParts.length);i++){var c2=cParts[i]||0,l2=lParts[i]||0;if(c2>l2){isAhead=true;break;}if(c2<l2)break;}}
|
|
1028
|
-
if(isNewer){
|
|
1029
|
-
var breaking=(lParts[0]!==cParts[0]||lParts[1]!==cParts[1]);
|
|
1030
|
-
el.innerHTML='Latest: <strong style="color:#f9e2af;">v'+latest+'</strong>';
|
|
1031
|
-
if(breaking){
|
|
1032
|
-
el.innerHTML+='<div style="color:#f38ba8;margin-top:6px;">⚠ Major/minor version change — check the <a href="https://github.com/tina4stack/tina4-ruby/releases" target="_blank" style="color:#89b4fa;">changelog</a> for breaking changes before upgrading.</div>';
|
|
1033
|
-
}else{
|
|
1034
|
-
el.innerHTML+='<div style="color:#f9e2af;margin-top:6px;">Patch update available. Run: <code style="background:#313244;padding:2px 6px;border-radius:3px;">gem install tina4ruby</code></div>';
|
|
1035
|
-
}
|
|
1036
|
-
}else if(isAhead){
|
|
1037
|
-
el.innerHTML='You are running <strong style="color:#cba6f7;">v'+current+'</strong> (ahead of RubyGems <strong>v'+latest+'</strong> — not yet published).';
|
|
1038
|
-
el.style.color='#cba6f7';
|
|
1039
|
-
}else{
|
|
1040
|
-
el.innerHTML='Latest: <strong style="color:#a6e3a1;">v'+latest+'</strong> — You are up to date!';
|
|
1041
|
-
el.style.color='#a6e3a1';
|
|
1042
|
-
}
|
|
1096
|
+
var dash = document.getElementById('tina4-dash-link');
|
|
1097
|
+
if (dash) { dash.addEventListener('click', toggleOverlay); }
|
|
1098
|
+
try {
|
|
1099
|
+
if (location.pathname.indexOf('/__dev') !== 0
|
|
1100
|
+
&& localStorage.getItem(STATE_KEY) === '1'
|
|
1101
|
+
&& !document.getElementById('tina4-dev-panel')) {
|
|
1102
|
+
buildOverlay();
|
|
1043
1103
|
}
|
|
1044
|
-
})
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
body + "\n" + toolbar
|
|
1058
|
-
end
|
|
1059
|
-
end
|
|
1060
|
-
|
|
1061
|
-
# WebSocket-primary dev reloader injected into HTML pages in debug mode.
|
|
1062
|
-
#
|
|
1063
|
-
# The running server re-imports changed src/ route files in-process and
|
|
1064
|
-
# pushes a {type,file,mtime} message over /__dev_reload — no respawn,
|
|
1065
|
-
# instant refresh. The mtime poll is a FALLBACK only: it is started when
|
|
1066
|
-
# the socket is down and stopped the moment it connects. On a CSS change
|
|
1067
|
-
# the client swaps <link rel=stylesheet> hrefs with a cache-bust query;
|
|
1068
|
-
# any other change does a full location.reload(). The poll seeds its
|
|
1069
|
-
# last-seen mtime to a null sentinel (NOT 0) and reloads whenever the
|
|
1070
|
-
# polled mtime DIFFERS (not just when greater) so the first change after
|
|
1071
|
-
# load isn't swallowed and a counter reset on restart still triggers.
|
|
1072
|
-
# Mirrors the Python master's injected client exactly.
|
|
1073
|
-
def dev_reload_client_js
|
|
1074
|
-
poll_interval_ms = (ENV["TINA4_DEV_POLL_INTERVAL"] || "3000").to_i
|
|
1075
|
-
poll_interval_ms = 3000 if poll_interval_ms <= 0
|
|
1076
|
-
<<~JS
|
|
1077
|
-
(function(){
|
|
1078
|
-
var _t4_css_exts=['.css','.scss'],_t4_debounce=null;
|
|
1079
|
-
var _t4_interval=parseInt('#{poll_interval_ms}')||3000;
|
|
1080
|
-
var _t4_ws=null,_t4_poll_timer=null,_t4_mtime=null;
|
|
1081
|
-
function _t4_apply(d){
|
|
1082
|
-
d=d||{};
|
|
1083
|
-
var f=d.file||'',t=d.type||'';
|
|
1084
|
-
var isCss=t==='css'||_t4_css_exts.some(function(e){return f.endsWith(e)});
|
|
1085
|
-
if(isCss){
|
|
1086
|
-
var links=document.querySelectorAll('link[rel="stylesheet"]');
|
|
1087
|
-
links.forEach(function(l){
|
|
1088
|
-
var href=l.getAttribute('href');
|
|
1089
|
-
if(href){l.setAttribute('href',href.split('?')[0]+'?_t4='+(d.mtime||Date.now()))}
|
|
1104
|
+
} catch (_) {}
|
|
1105
|
+
|
|
1106
|
+
if (bar.getAttribute('data-reload') !== '1') { return; }
|
|
1107
|
+
var cssExts = ['.css', '.scss'], debounce = null, interval = #{poll_interval_ms};
|
|
1108
|
+
var ws = null, pollTimer = null, mtime = null;
|
|
1109
|
+
function apply(d) {
|
|
1110
|
+
d = d || {};
|
|
1111
|
+
var f = d.file || '', t = d.type || '';
|
|
1112
|
+
var isCss = t === 'css' || cssExts.some(function (e) { return f.endsWith(e); });
|
|
1113
|
+
if (isCss) {
|
|
1114
|
+
document.querySelectorAll('link[rel="stylesheet"]').forEach(function (l) {
|
|
1115
|
+
var href = l.getAttribute('href');
|
|
1116
|
+
if (href) { l.setAttribute('href', href.split('?')[0] + '?_t4=' + (d.mtime || Date.now())); }
|
|
1090
1117
|
});
|
|
1091
|
-
}else{
|
|
1118
|
+
} else {
|
|
1092
1119
|
location.reload();
|
|
1093
1120
|
}
|
|
1094
1121
|
}
|
|
1095
|
-
function
|
|
1096
|
-
fetch('/__dev/api/mtime').then(function(r){return r.json()}).then(function(d){
|
|
1097
|
-
if(
|
|
1098
|
-
if(d.mtime!==
|
|
1099
|
-
|
|
1100
|
-
if(_t4_debounce)clearTimeout(_t4_debounce);
|
|
1101
|
-
_t4_debounce=setTimeout(function(){_t4_apply(d);},500);
|
|
1102
|
-
}
|
|
1103
|
-
}).catch(function(){});
|
|
1104
|
-
}
|
|
1105
|
-
function _t4_startPoll(){
|
|
1106
|
-
if(_t4_poll_timer)return;
|
|
1107
|
-
_t4_mtime=null;
|
|
1108
|
-
_t4_poll_timer=setInterval(_t4_poll,_t4_interval);
|
|
1109
|
-
}
|
|
1110
|
-
function _t4_stopPoll(){
|
|
1111
|
-
if(_t4_poll_timer){clearInterval(_t4_poll_timer);_t4_poll_timer=null;}
|
|
1122
|
+
function poll() {
|
|
1123
|
+
fetch('/__dev/api/mtime').then(function (r) { return r.json(); }).then(function (d) {
|
|
1124
|
+
if (mtime === null) { mtime = d.mtime; return; }
|
|
1125
|
+
if (d.mtime !== mtime) { mtime = d.mtime; if (debounce) { clearTimeout(debounce); } debounce = setTimeout(function () { apply(d); }, 500); }
|
|
1126
|
+
}).catch(function () {});
|
|
1112
1127
|
}
|
|
1113
|
-
function
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1128
|
+
function startPoll() { if (pollTimer) { return; } mtime = null; pollTimer = setInterval(poll, interval); }
|
|
1129
|
+
function stopPoll() { if (pollTimer) { clearInterval(pollTimer); pollTimer = null; } }
|
|
1130
|
+
function connect() {
|
|
1131
|
+
var url = (location.protocol === 'https:' ? 'wss' : 'ws') + '://' + location.host + '/__dev_reload';
|
|
1132
|
+
try { ws = new WebSocket(url); } catch (_) { startPoll(); return; }
|
|
1133
|
+
ws.addEventListener('open', function () { stopPoll(); });
|
|
1134
|
+
ws.addEventListener('message', function (ev) {
|
|
1135
|
+
var d = null;
|
|
1136
|
+
try { d = typeof ev.data === 'string' ? JSON.parse(ev.data) : null; } catch (_) {}
|
|
1137
|
+
if (!d) { return; }
|
|
1138
|
+
if (d.type === 'reload' || d.type === 'change' || d.type === 'css') {
|
|
1139
|
+
if (debounce) { clearTimeout(debounce); }
|
|
1140
|
+
debounce = setTimeout(function () { apply(d); }, 150);
|
|
1124
1141
|
}
|
|
1125
1142
|
});
|
|
1126
|
-
|
|
1127
|
-
|
|
1143
|
+
ws.addEventListener('close', function () { ws = null; startPoll(); setTimeout(connect, 2000); });
|
|
1144
|
+
ws.addEventListener('error', function () { try { ws && ws.close(); } catch (_) {} });
|
|
1128
1145
|
}
|
|
1129
|
-
|
|
1146
|
+
connect();
|
|
1130
1147
|
})();
|
|
1131
1148
|
JS
|
|
1132
1149
|
end
|
data/lib/tina4/version.rb
CHANGED