|
|
@@ -284,6 +284,34 @@ body.dark-mode .cp-btn.red{color:#ff6b6b;}
|
|
|
/* ── Connection lost ── */
|
|
|
#conndrop{position:fixed;top:max(env(safe-area-inset-top),8px);left:50%;transform:translateX(-50%);z-index:200;background:rgba(196,43,28,.9);backdrop-filter:blur(8px);border-radius:20px;padding:5px 13px;display:none;align-items:center;gap:7px;font-size:12px;color:#fff;}
|
|
|
#conndrop img{width:14px;height:14px;}
|
|
|
+
|
|
|
+/* ── FW Tab Context Menu ── */
|
|
|
+#fw-ctx-overlay{position:fixed;inset:0;z-index:210;display:none;}
|
|
|
+#fw-ctx-overlay.on{display:block;}
|
|
|
+#fw-ctx-menu{
|
|
|
+ position:fixed;z-index:211;display:none;
|
|
|
+ background:rgba(235,235,235,.94);backdrop-filter:blur(40px) saturate(200%);-webkit-backdrop-filter:blur(40px) saturate(200%);
|
|
|
+ border-radius:16px;box-shadow:0 8px 32px rgba(0,0,0,.32);
|
|
|
+ min-width:220px;overflow:hidden;
|
|
|
+ transform-origin:top left;
|
|
|
+ animation:fw-ctx-in .14s ease;
|
|
|
+}
|
|
|
+body.dark-mode #fw-ctx-menu{background:rgba(30,30,30,.96);}
|
|
|
+@keyframes fw-ctx-in{from{opacity:0;transform:scale(.88);}to{opacity:1;transform:scale(1);}}
|
|
|
+.fw-ctx-item{
|
|
|
+ display:flex;align-items:center;gap:12px;
|
|
|
+ padding:13px 16px;font-size:14px;color:#111;cursor:pointer;
|
|
|
+ border:none;background:none;width:100%;text-align:left;
|
|
|
+ -webkit-tap-highlight-color:transparent;transition:background .1s;
|
|
|
+}
|
|
|
+body.dark-mode .fw-ctx-item{color:#f0f0f0;}
|
|
|
+.fw-ctx-item:active{background:rgba(0,0,0,.07);}
|
|
|
+body.dark-mode .fw-ctx-item:active{background:rgba(255,255,255,.09);}
|
|
|
+.fw-ctx-item+.fw-ctx-item{border-top:1px solid rgba(0,0,0,.06);}
|
|
|
+body.dark-mode .fw-ctx-item+.fw-ctx-item{border-top-color:rgba(255,255,255,.08);}
|
|
|
+.fw-ctx-item.red{color:#c42b1c;}
|
|
|
+body.dark-mode .fw-ctx-item.red{color:#ff6b6b;}
|
|
|
+.fw-ctx-item svg{flex-shrink:0;opacity:.75;}
|
|
|
</style>
|
|
|
</head>
|
|
|
<body>
|
|
|
@@ -433,6 +461,33 @@ body.dark-mode .cp-btn.red{color:#ff6b6b;}
|
|
|
<div id="fw-body"></div>
|
|
|
</div>
|
|
|
|
|
|
+<!-- FW tab context menu -->
|
|
|
+<div id="fw-ctx-overlay" onclick="fwCtxClose()"></div>
|
|
|
+<div id="fw-ctx-menu">
|
|
|
+ <button class="fw-ctx-item" onclick="fwCtxNewTab()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
|
+ <path d="M6 3H3a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h9a1 1 0 0 0 1-1v-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
|
|
+ <path d="M9 2h5v5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
|
+ <path d="M14 2L8 8" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
|
|
+ </svg>
|
|
|
+ Open in New Tab
|
|
|
+ </button>
|
|
|
+ <button class="fw-ctx-item" onclick="fwCtxCopy()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
|
+ <rect x="5" y="5" width="9" height="10" rx="1.5" stroke="currentColor" stroke-width="1.4"/>
|
|
|
+ <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-7A1.5 1.5 0 0 0 1 3.5v7A1.5 1.5 0 0 0 2.5 12H4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
|
|
+ </svg>
|
|
|
+ Copy URL
|
|
|
+ </button>
|
|
|
+ <button class="fw-ctx-item red" onclick="fwCtxCloseWindow()">
|
|
|
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
|
|
|
+ <circle cx="8" cy="8" r="6.5" stroke="currentColor" stroke-width="1.4"/>
|
|
|
+ <path d="M5.5 5.5l5 5M10.5 5.5l-5 5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/>
|
|
|
+ </svg>
|
|
|
+ Close Window
|
|
|
+ </button>
|
|
|
+</div>
|
|
|
+
|
|
|
<!-- Connection indicator -->
|
|
|
<div id="conndrop">
|
|
|
<img src="SystemAO/desktop/icons/connlost.svg">
|
|
|
@@ -1112,6 +1167,112 @@ function connCheck(){
|
|
|
───────────────────────────────────────────── */
|
|
|
function escH(s){return $("<span>").text(s||"").html();}
|
|
|
function escA(s){return(s||"").replace(/"/g,""").replace(/'/g,"'");}
|
|
|
+
|
|
|
+/* ─────────────────────────────────────────────
|
|
|
+ FW TAB LONG-PRESS CONTEXT MENU
|
|
|
+───────────────────────────────────────────── */
|
|
|
+var _fwCtxWid = null;
|
|
|
+var _fwLongPressTimer = null;
|
|
|
+var _fwLongPressFired = false;
|
|
|
+
|
|
|
+(function(){
|
|
|
+ var fwTabs = document.getElementById("fw-tabs");
|
|
|
+ var startX, startY;
|
|
|
+
|
|
|
+ fwTabs.addEventListener("touchstart", function(e){
|
|
|
+ var tab = e.target.closest(".fw-tab");
|
|
|
+ if(!tab || e.target.closest(".fw-tab-x")) return;
|
|
|
+ var wid = tab.getAttribute("data-wid");
|
|
|
+ var touch = e.touches[0];
|
|
|
+ startX = touch.clientX; startY = touch.clientY;
|
|
|
+ _fwLongPressFired = false;
|
|
|
+ _fwLongPressTimer = setTimeout(function(){
|
|
|
+ _fwLongPressFired = true;
|
|
|
+ fwCtxShow(wid, startX, startY);
|
|
|
+ if(navigator.vibrate) navigator.vibrate(35);
|
|
|
+ }, 500);
|
|
|
+ }, {passive: true});
|
|
|
+
|
|
|
+ fwTabs.addEventListener("touchmove", function(e){
|
|
|
+ if(!_fwLongPressTimer) return;
|
|
|
+ var t = e.touches[0];
|
|
|
+ if(Math.abs(t.clientX - startX) > 8 || Math.abs(t.clientY - startY) > 8){
|
|
|
+ clearTimeout(_fwLongPressTimer);
|
|
|
+ _fwLongPressTimer = null;
|
|
|
+ }
|
|
|
+ }, {passive: true});
|
|
|
+
|
|
|
+ fwTabs.addEventListener("touchend", function(){
|
|
|
+ clearTimeout(_fwLongPressTimer);
|
|
|
+ _fwLongPressTimer = null;
|
|
|
+ }, {passive: true});
|
|
|
+
|
|
|
+ // Suppress the click that follows a long-press
|
|
|
+ fwTabs.addEventListener("click", function(e){
|
|
|
+ if(_fwLongPressFired){
|
|
|
+ _fwLongPressFired = false;
|
|
|
+ e.stopPropagation();
|
|
|
+ e.preventDefault();
|
|
|
+ }
|
|
|
+ }, true);
|
|
|
+})();
|
|
|
+
|
|
|
+function fwCtxShow(wid, x, y){
|
|
|
+ _fwCtxWid = wid;
|
|
|
+ var menu = document.getElementById("fw-ctx-menu");
|
|
|
+ var menuW = 224, menuH = 150;
|
|
|
+ var left = Math.min(x, window.innerWidth - menuW - 10);
|
|
|
+ var top = Math.min(y, window.innerHeight - menuH - 10);
|
|
|
+ left = Math.max(10, left);
|
|
|
+ top = Math.max(10, top);
|
|
|
+ menu.style.left = left + "px";
|
|
|
+ menu.style.top = top + "px";
|
|
|
+ document.getElementById("fw-ctx-overlay").classList.add("on");
|
|
|
+ menu.style.display = "block";
|
|
|
+ // re-trigger animation
|
|
|
+ menu.style.animation = "none";
|
|
|
+ menu.offsetHeight; // reflow
|
|
|
+ menu.style.animation = "";
|
|
|
+}
|
|
|
+
|
|
|
+function fwCtxClose(){
|
|
|
+ _fwCtxWid = null;
|
|
|
+ document.getElementById("fw-ctx-overlay").classList.remove("on");
|
|
|
+ document.getElementById("fw-ctx-menu").style.display = "none";
|
|
|
+}
|
|
|
+
|
|
|
+function fwCtxGetUrl(){
|
|
|
+ if(!_fwCtxWid) return null;
|
|
|
+ var fw = getFloatWindowByID(_fwCtxWid);
|
|
|
+ if(!fw) return null;
|
|
|
+ try{ return fw.find("iframe")[0].src || null; }catch(e){ return null; }
|
|
|
+}
|
|
|
+
|
|
|
+function fwCtxNewTab(){
|
|
|
+ var url = fwCtxGetUrl();
|
|
|
+ fwCtxClose();
|
|
|
+ if(url) window.open(url, "_blank");
|
|
|
+}
|
|
|
+
|
|
|
+function fwCtxCopy(){
|
|
|
+ var url = fwCtxGetUrl();
|
|
|
+ fwCtxClose();
|
|
|
+ if(!url) return;
|
|
|
+ if(navigator.clipboard){
|
|
|
+ navigator.clipboard.writeText(url).catch(function(){});
|
|
|
+ } else {
|
|
|
+ var ta = document.createElement("textarea");
|
|
|
+ ta.value = url; ta.style.cssText = "position:fixed;opacity:0";
|
|
|
+ document.body.appendChild(ta); ta.select();
|
|
|
+ document.execCommand("copy"); document.body.removeChild(ta);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function fwCtxCloseWindow(){
|
|
|
+ var wid = _fwCtxWid;
|
|
|
+ fwCtxClose();
|
|
|
+ if(wid) closeFloatWindow(wid);
|
|
|
+}
|
|
|
</script>
|
|
|
</body>
|
|
|
</html>
|