@kangsicj/nexussync 1.1.0 → 1.2.0
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.
- package/README.md +1 -1
- package/dist/NexusSync.meta.js +3 -5
- package/dist/NexusSync.user.js +80 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
<br>
|
|
6
6
|
|
|
7
|
-
[](./CHANGELOG.md)
|
|
8
8
|
[](https://www.tampermonkey.net/)
|
|
9
9
|
[](https://apps.apple.com/app/userscripts/id1463298887)
|
|
10
10
|
[](https://fairy.hada.io/@jinny)
|
package/dist/NexusSync.meta.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// ==UserScript==
|
|
2
|
-
// @name NexusSync
|
|
3
|
-
// @name:ko 넥서스싱크
|
|
4
|
-
// @name:nl-BE NexusSync Ultimate: Canvas & Media Synchronisatie
|
|
2
|
+
// @name NexusSync
|
|
3
|
+
// @name:ko NexusSync (넥서스싱크)
|
|
5
4
|
// @namespace https://github.com/kangsicj/NexusSync
|
|
6
5
|
// @author kangsicj
|
|
7
6
|
// @homepageURL https://github.com/kangsicj/NexusSync
|
|
@@ -9,7 +8,7 @@
|
|
|
9
8
|
// @icon https://cdn.jsdelivr.net/npm/@kangsicj/nexussync@latest/assets/icon.svg
|
|
10
9
|
// @updateURL https://cdn.jsdelivr.net/npm/@kangsicj/nexussync@latest/dist/NexusSync.meta.js
|
|
11
10
|
// @downloadURL https://cdn.jsdelivr.net/npm/@kangsicj/nexussync@latest/dist/NexusSync.user.js
|
|
12
|
-
// @version 1.
|
|
11
|
+
// @version 1.2.0
|
|
13
12
|
// @description Watch any web video together in perfect sync: cinema mode, live AI subtitles & translation, chat bubbles with auto-translation, quick ink, cover, go-together to the next episode.
|
|
14
13
|
// @description:ko 어떤 웹 영상이든 완벽한 싱크로 함께 보기: 시네마 모드, 실시간 AI 자막·번역, 자동 번역 말풍선 채팅, 잠깐 그리기, 가리기, 다음 화도 함께.
|
|
15
14
|
// @match *://*/*
|
|
@@ -32,5 +31,4 @@
|
|
|
32
31
|
// @connect api.anthropic.com
|
|
33
32
|
// @connect api-free.deepl.com
|
|
34
33
|
// @connect api.deepl.com
|
|
35
|
-
// @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js#sha256=95f4d1cbf099f57161b664bc048426ec3df92637801a4c79116e83315aa787e7
|
|
36
34
|
// ==/UserScript==
|
package/dist/NexusSync.user.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// ==UserScript==
|
|
2
|
-
// @name NexusSync
|
|
3
|
-
// @name:ko 넥서스싱크
|
|
4
|
-
// @name:nl-BE NexusSync Ultimate: Canvas & Media Synchronisatie
|
|
2
|
+
// @name NexusSync
|
|
3
|
+
// @name:ko NexusSync (넥서스싱크)
|
|
5
4
|
// @namespace https://github.com/kangsicj/NexusSync
|
|
6
5
|
// @author kangsicj
|
|
7
6
|
// @homepageURL https://github.com/kangsicj/NexusSync
|
|
@@ -9,7 +8,7 @@
|
|
|
9
8
|
// @icon https://cdn.jsdelivr.net/npm/@kangsicj/nexussync@latest/assets/icon.svg
|
|
10
9
|
// @updateURL https://cdn.jsdelivr.net/npm/@kangsicj/nexussync@latest/dist/NexusSync.meta.js
|
|
11
10
|
// @downloadURL https://cdn.jsdelivr.net/npm/@kangsicj/nexussync@latest/dist/NexusSync.user.js
|
|
12
|
-
// @version 1.
|
|
11
|
+
// @version 1.2.0
|
|
13
12
|
// @description Watch any web video together in perfect sync: cinema mode, live AI subtitles & translation, chat bubbles with auto-translation, quick ink, cover, go-together to the next episode.
|
|
14
13
|
// @description:ko 어떤 웹 영상이든 완벽한 싱크로 함께 보기: 시네마 모드, 실시간 AI 자막·번역, 자동 번역 말풍선 채팅, 잠깐 그리기, 가리기, 다음 화도 함께.
|
|
15
14
|
// @match *://*/*
|
|
@@ -32,20 +31,23 @@
|
|
|
32
31
|
// @connect api.anthropic.com
|
|
33
32
|
// @connect api-free.deepl.com
|
|
34
33
|
// @connect api.deepl.com
|
|
35
|
-
// @require https://cdnjs.cloudflare.com/ajax/libs/lz-string/1.5.0/lz-string.min.js#sha256=95f4d1cbf099f57161b664bc048426ec3df92637801a4c79116e83315aa787e7
|
|
36
34
|
// ==/UserScript==
|
|
37
35
|
|
|
38
36
|
(async function () {
|
|
39
37
|
'use strict';
|
|
40
38
|
|
|
39
|
+
// lz-string 1.5.0 — MIT License, © pieroxy (https://github.com/pieroxy/lz-string). Bundled so
|
|
40
|
+
// no extra file has to be fetched (and verified) by the userscript manager.
|
|
41
|
+
const LZString = (() => { var LZString=function(){var r=String.fromCharCode,o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+-$",e={};function t(r,o){if(!e[r]){e[r]={};for(var n=0;n<r.length;n++)e[r][r.charAt(n)]=n}return e[r][o]}var i={compressToBase64:function(r){if(null==r)return"";var n=i._compress(r,6,function(r){return o.charAt(r)});switch(n.length%4){default:case 0:return n;case 1:return n+"===";case 2:return n+"==";case 3:return n+"="}},decompressFromBase64:function(r){return null==r?"":""==r?null:i._decompress(r.length,32,function(n){return t(o,r.charAt(n))})},compressToUTF16:function(o){return null==o?"":i._compress(o,15,function(o){return r(o+32)})+" "},decompressFromUTF16:function(r){return null==r?"":""==r?null:i._decompress(r.length,16384,function(o){return r.charCodeAt(o)-32})},compressToUint8Array:function(r){for(var o=i.compress(r),n=new Uint8Array(2*o.length),e=0,t=o.length;e<t;e++){var s=o.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null==o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;e<t;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(r){return null==r?"":i._compress(r,6,function(r){return n.charAt(r)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(o){return t(n,r.charAt(o))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(r,o,n){if(null==r)return"";var e,t,i,s={},u={},a="",p="",c="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;i<r.length;i+=1)if(a=r.charAt(i),Object.prototype.hasOwnProperty.call(s,a)||(s[a]=f++,u[a]=!0),p=c+a,Object.prototype.hasOwnProperty.call(s,p))c=p;else{if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e<h;e++)m<<=1,v==o-1?(v=0,d.push(n(m)),m=0):v++;for(t=c.charCodeAt(0),e=0;e<8;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;e<h;e++)m=m<<1|t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=c.charCodeAt(0),e=0;e<16;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;0==--l&&(l=Math.pow(2,h),h++),s[p]=f++,c=String(a)}if(""!==c){if(Object.prototype.hasOwnProperty.call(u,c)){if(c.charCodeAt(0)<256){for(e=0;e<h;e++)m<<=1,v==o-1?(v=0,d.push(n(m)),m=0):v++;for(t=c.charCodeAt(0),e=0;e<8;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;e<h;e++)m=m<<1|t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=c.charCodeAt(0),e=0;e<16;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}0==--l&&(l=Math.pow(2,h),h++),delete u[c]}else for(t=s[c],e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;0==--l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;e<h;e++)m=m<<1|1&t,v==o-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==o-1){d.push(n(m));break}v++}return d.join("")},decompress:function(r){return null==r?"":""==r?null:i._decompress(r.length,32768,function(o){return r.charCodeAt(o)})},_decompress:function(o,n,e){var t,i,s,u,a,p,c,l=[],f=4,h=4,d=3,m="",v=[],g={val:e(0),position:n,index:1};for(t=0;t<3;t+=1)l[t]=t;for(s=0,a=Math.pow(2,2),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;c=r(s);break;case 2:return""}for(l[3]=c,i=c,v.push(c);;){if(g.index>o)return"";for(s=0,a=Math.pow(2,d),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;switch(c=s){case 0:for(s=0,a=Math.pow(2,8),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 1:for(s=0,a=Math.pow(2,16),p=1;p!=a;)u=g.val&g.position,g.position>>=1,0==g.position&&(g.position=n,g.val=e(g.index++)),s|=(u>0?1:0)*p,p<<=1;l[h++]=r(s),c=h-1,f--;break;case 2:return v.join("")}if(0==f&&(f=Math.pow(2,d),d++),l[c])m=l[c];else{if(c!==h)return null;m=i+i.charAt(0)}v.push(m),l[h++]=i+m.charAt(0),i=m,0==--f&&(f=Math.pow(2,d),d++)}}};return i}(); return LZString; })();
|
|
42
|
+
|
|
41
43
|
// The version of the code that is actually running (the manager's metadata can lag
|
|
42
44
|
// behind when an old copy is still installed).
|
|
43
|
-
const NXU_VERSION = '1.
|
|
45
|
+
const NXU_VERSION = '1.2.0';
|
|
44
46
|
// Rank used by the one-instance guard below and to tell newer builds apart in a room.
|
|
45
47
|
// It is independent of the public version (1.0.0 is the first public release, after
|
|
46
48
|
// private builds that reached ranks up to 10.2.0), so every older copy still installed
|
|
47
49
|
// steps aside instead of this release.
|
|
48
|
-
const NXU_RANK = '11.
|
|
50
|
+
const NXU_RANK = '11.2.0';
|
|
49
51
|
const NXU_SESSION = Math.random().toString(36).slice(2, 10); // this page load // raise with every release
|
|
50
52
|
// App icon (same artwork as assets/icon.svg / @icon), inlined so it shows offline too.
|
|
51
53
|
const NXU_ICON = 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNTYgMjU2IiByb2xlPSJpbWciIGFyaWEtbGFiZWw9Ik5leHVzU3luYyI+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJueGJnIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNWIyZWZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmY0ZmEzIi8+PC9saW5lYXJHcmFkaWVudD48cmFkaWFsR3JhZGllbnQgaWQ9Im54Z2xvdyIgY3g9IjAuODIiIGN5PSIwLjE0IiByPSIwLjciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzdjZjlmZiIgc3RvcC1vcGFjaXR5PSIwLjU1Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjN2NmOWZmIiBzdG9wLW9wYWNpdHk9IjAiLz48L3JhZGlhbEdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0ibnhwbGF5IiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZmZmZmZmIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjZmZlM2YxIi8+PC9saW5lYXJHcmFkaWVudD48Y2xpcFBhdGggaWQ9Im54ciI+PHJlY3Qgd2lkdGg9IjI1NiIgaGVpZ2h0PSIyNTYiIHJ4PSI1OCIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNueHIpIj48cmVjdCB3aWR0aD0iMjU2IiBoZWlnaHQ9IjI1NiIgZmlsbD0idXJsKCNueGJnKSIvPjxyZWN0IHdpZHRoPSIyNTYiIGhlaWdodD0iMjU2IiBmaWxsPSJ1cmwoI254Z2xvdykiLz48ZyBmaWxsPSJub25lIiBzdHJva2U9IiNmZmZmZmYiIHN0cm9rZS13aWR0aD0iMTUiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0iTTYwIDExNiBBNzAgNzAgMCAwIDEgMTc4IDcyIi8+PHBhdGggZD0iTTE5NiAxNDAgQTcwIDcwIDAgMCAxIDc4IDE4NCIvPjwvZz48cGF0aCBkPSJNMTg2IDUwIEwyMDAgOTIgTDE1NyA4NiBaIiBmaWxsPSIjZmZmZmZmIi8+PHBhdGggZD0iTTcwIDIwNiBMNTYgMTY0IEw5OSAxNzAgWiIgZmlsbD0iI2ZmZmZmZiIvPjxwYXRoIGQ9Ik0xMTAgOTIgUTEwNCA4OCAxMDQgOTYgTDEwNCAxNjAgUTEwNCAxNjggMTEwIDE2NCBMMTYyIDEzMiBRMTY4IDEyOCAxNjIgMTI0IFoiIGZpbGw9InVybCgjbnhwbGF5KSIvPjwvZz48L3N2Zz4=';
|
|
@@ -3839,6 +3841,11 @@
|
|
|
3839
3841
|
|
|
3840
3842
|
function _writeVideoStateFields(state) {
|
|
3841
3843
|
yVideo.set('state', _stateForDoc(state));
|
|
3844
|
+
// Each device also keeps its latest decision under its own key. Two devices writing the
|
|
3845
|
+
// shared 'state' at the same moment are settled by Yjs in a fixed order — on a slow link
|
|
3846
|
+
// the same device always won, so the other one's pause could never get through. Keys
|
|
3847
|
+
// that only one device writes are never lost; readers pick the newest decision.
|
|
3848
|
+
yVideo.set(DEVICE_KEY_PREFIX + STATE.userId, _stateForDoc(state));
|
|
3842
3849
|
// Legacy mirror for clients that have not upgraded yet.
|
|
3843
3850
|
yVideo.set('eventId', state.eventId);
|
|
3844
3851
|
yVideo.set('sourceDeviceId', state.sourceDeviceId);
|
|
@@ -3885,8 +3892,12 @@
|
|
|
3885
3892
|
function _repairAcceptedVideoState() {
|
|
3886
3893
|
const accepted = STATE._acceptedVideoState;
|
|
3887
3894
|
if (!accepted || !ydoc || !yVideo) return;
|
|
3895
|
+
// Our own key carries what this device holds as the decision; once it shows the
|
|
3896
|
+
// accepted one, every reader can see it and there is nothing to rewrite.
|
|
3897
|
+
const own = _normalizeVideoState(yVideo.get(DEVICE_KEY_PREFIX + STATE.userId));
|
|
3898
|
+
if (own && own.eventId === accepted.eventId) return;
|
|
3888
3899
|
const current = _normalizeVideoState(yVideo.get('state'));
|
|
3889
|
-
if (current && current.eventId === accepted.eventId) return;
|
|
3900
|
+
if (current && current.eventId === accepted.eventId && own) return;
|
|
3890
3901
|
// Writing back is only for a decision a stale writer covered up, and at most every
|
|
3891
3902
|
// 3 s: two devices that disagree must not keep rewriting the doc at each other.
|
|
3892
3903
|
if (accepted.action === 'heartbeat') return;
|
|
@@ -3897,13 +3908,29 @@
|
|
|
3897
3908
|
logDebug(`[Authority] Restored accepted media state ${accepted.eventId}`, 'warn');
|
|
3898
3909
|
}
|
|
3899
3910
|
|
|
3911
|
+
const DEVICE_KEY_PREFIX = 's:';
|
|
3912
|
+
// The newest decision among the shared key and every device's own key.
|
|
3913
|
+
function _newestVideoState(keys) {
|
|
3914
|
+
let best = null;
|
|
3915
|
+
for (const key of keys) {
|
|
3916
|
+
if (key !== 'state' && !(key.startsWith(DEVICE_KEY_PREFIX) && key !== DEVICE_KEY_PREFIX + STATE.userId)) continue;
|
|
3917
|
+
const st = _normalizeVideoState(yVideo.get(key));
|
|
3918
|
+
if (st && (!best || _isVideoStateNewer(st, best))) best = st;
|
|
3919
|
+
}
|
|
3920
|
+
return best;
|
|
3921
|
+
}
|
|
3900
3922
|
function _readObservedVideoState(event) {
|
|
3901
3923
|
if (!yVideo) return null;
|
|
3902
|
-
if (event && event.keysChanged
|
|
3903
|
-
|
|
3924
|
+
if (event && event.keysChanged) {
|
|
3925
|
+
const keys = [...event.keysChanged].filter(k => k === 'state' || k.startsWith(DEVICE_KEY_PREFIX));
|
|
3926
|
+
if (keys.length) return _newestVideoState(keys);
|
|
3904
3927
|
}
|
|
3905
3928
|
return _readLegacyVideoState();
|
|
3906
3929
|
}
|
|
3930
|
+
function _currentDocVideoState() {
|
|
3931
|
+
if (!yVideo) return null;
|
|
3932
|
+
return _newestVideoState([...yVideo.keys()]) || _readLegacyVideoState();
|
|
3933
|
+
}
|
|
3907
3934
|
|
|
3908
3935
|
function _acceptRemoteVideoState(rawState) {
|
|
3909
3936
|
const state = _normalizeVideoState(rawState);
|
|
@@ -3927,7 +3954,7 @@
|
|
|
3927
3954
|
|
|
3928
3955
|
function _getVideoState() {
|
|
3929
3956
|
if (!yVideo) return STATE._acceptedVideoState;
|
|
3930
|
-
const current =
|
|
3957
|
+
const current = _currentDocVideoState();
|
|
3931
3958
|
if (!STATE._acceptedVideoState) return current;
|
|
3932
3959
|
return _isVideoStateNewer(current, STATE._acceptedVideoState) ? current : STATE._acceptedVideoState;
|
|
3933
3960
|
}
|
|
@@ -4920,7 +4947,7 @@
|
|
|
4920
4947
|
clearTimeout(STATE._deferredObserve);
|
|
4921
4948
|
STATE._deferredObserve = setTimeout(() => {
|
|
4922
4949
|
if (!yVideo) return;
|
|
4923
|
-
_onObservedVideoState(
|
|
4950
|
+
_onObservedVideoState(_currentDocVideoState(), true);
|
|
4924
4951
|
}, Math.max(50, STATE.localIntentUntil - Date.now() + 60));
|
|
4925
4952
|
return;
|
|
4926
4953
|
}
|
|
@@ -6579,6 +6606,8 @@
|
|
|
6579
6606
|
.sync-bubble { position:absolute; top:20px; left:50%; transform:translate(-50%,-20px); background:var(--glass-strong); color:#fff; padding:10px 22px; border-radius:24px; border-left:4px solid var(--accent); font-weight:600; opacity:0; transition:opacity 0.4s, transform 0.4s; z-index:1900; white-space:nowrap; }
|
|
6580
6607
|
.sync-bubble.visible { opacity:1; transform:translate(-50%,0); }
|
|
6581
6608
|
/* bubbles */
|
|
6609
|
+
.nexus-container.nx-composing .nx-bubbles { top:calc(70px + var(--nx-compose-h, 120px) + env(safe-area-inset-top, 0px)); }
|
|
6610
|
+
.nx-bubbles { transition:top 0.25s ease; }
|
|
6582
6611
|
.nx-bubbles { position:absolute; top:calc(64px + env(safe-area-inset-top, 0px)); left:calc(12px + env(safe-area-inset-left, 0px)); z-index:1950; display:flex; flex-direction:column; gap:8px; width:min(300px, calc(100% - 24px)); pointer-events:none; }
|
|
6583
6612
|
.nx-bubble { pointer-events:auto; display:block; width:100%; text-align:left; border:none; cursor:pointer; color:#fff; font:inherit; background:rgba(18,20,26,0.52); -webkit-backdrop-filter:blur(3px); backdrop-filter:blur(3px); text-shadow:0 1px 2px rgba(0,0,0,0.8);
|
|
6584
6613
|
border-radius:16px 16px 16px 6px; padding:9px 12px 10px; box-shadow:0 8px 24px rgba(0,0,0,0.45); border-left:4px solid var(--c, #69f0ae); opacity:0; transform:translateX(-24px) scale(0.96); transition:opacity 0.25s ease, transform 0.25s ease; }
|
|
@@ -6627,13 +6656,16 @@
|
|
|
6627
6656
|
.nx-next-k { font-size:11px; font-weight:800; opacity:0.75; text-transform:uppercase; letter-spacing:0.04em; }
|
|
6628
6657
|
.nx-next-l { font-size:14px; font-weight:800; white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
|
|
6629
6658
|
.nx-next-x { flex:0 0 auto; width:28px; height:28px; border:none; border-radius:50%; background:rgba(255,255,255,0.12); color:#fff; cursor:pointer; font-size:12px; }
|
|
6630
|
-
|
|
6659
|
+
/* At the top: subtitles live at the bottom of the picture, and on phones the keyboard covers the bottom too. */
|
|
6660
|
+
.nx-compose { position:absolute; left:50%; top:calc(60px + env(safe-area-inset-top, 0px)); z-index:2060; width:min(520px, calc(100% - 24px)); transform:translate(-50%, -14px) scale(0.96); opacity:0; pointer-events:none;
|
|
6631
6661
|
background:rgba(16,18,24,0.58); -webkit-backdrop-filter:blur(4px); backdrop-filter:blur(4px); border-radius:18px; padding:8px 8px 9px; text-shadow:0 1px 2px rgba(0,0,0,0.75); box-shadow:0 14px 36px rgba(0,0,0,0.55), 0 0 0 1px color-mix(in srgb, var(--c, #69f0ae) 50%, transparent);
|
|
6632
6662
|
transition:opacity 0.22s ease, transform 0.25s cubic-bezier(.2,1.3,.4,1); display:flex; flex-direction:column; gap:6px; }
|
|
6633
6663
|
.nx-compose.open { opacity:1; transform:translate(-50%, 0) scale(1); pointer-events:auto; }
|
|
6634
6664
|
.nx-compose-head { display:flex; align-items:center; gap:6px; padding:0 2px 0 6px; }
|
|
6635
6665
|
.nx-compose-to { flex:1; min-width:0; font-size:12px; font-weight:800; color:var(--c, #69f0ae); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
|
|
6636
6666
|
.nx-compose-x { flex:0 0 auto; width:28px; height:28px; border:none; border-radius:50%; background:rgba(255,255,255,0.08); color:#fff; cursor:pointer; font-size:13px; font-family:inherit; }
|
|
6667
|
+
.nx-compose-sub { font-size:13px; line-height:1.35; padding:5px 10px; border-radius:10px; background:rgba(0,0,0,0.35); color:#fff; font-weight:600; overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; }
|
|
6668
|
+
.nx-compose-sub:empty { display:none; }
|
|
6637
6669
|
.nx-compose-chips { display:flex; gap:6px; overflow-x:auto; padding:0 2px; }
|
|
6638
6670
|
.nx-compose-row { display:flex; gap:6px; }
|
|
6639
6671
|
.nx-compose-input { flex:1; min-width:0; background:rgba(255,255,255,0.08); border:1px solid rgba(255,255,255,0.14); color:#fff; border-radius:999px; padding:9px 14px; font-size:16px; font-family:inherit; outline:none; }
|
|
@@ -7812,6 +7844,8 @@
|
|
|
7812
7844
|
};
|
|
7813
7845
|
head.append(me, to, full, x);
|
|
7814
7846
|
const chips = createEl('div', 'nx-compose-chips');
|
|
7847
|
+
// The line being spoken right now, so writing a message never means missing it.
|
|
7848
|
+
const subEcho = createEl('div', 'nx-compose-sub');
|
|
7815
7849
|
const row = createEl('div', 'nx-compose-row');
|
|
7816
7850
|
const input = createEl('input', 'nx-compose-input', '', { type: 'text', maxlength: '500', placeholder: t('talkPlaceholder'), enterkeyhint: 'send', autocomplete: 'off' });
|
|
7817
7851
|
const send = createEl('button', 'nx-compose-send', '➤', { type: 'button', 'aria-label': t('talkSend') });
|
|
@@ -7823,7 +7857,7 @@
|
|
|
7823
7857
|
});
|
|
7824
7858
|
input.addEventListener('input', () => { SOCIAL.typing(); this._armClose(); });
|
|
7825
7859
|
row.append(input, send);
|
|
7826
|
-
wrap.append(head, chips, row);
|
|
7860
|
+
wrap.append(head, subEcho, chips, row);
|
|
7827
7861
|
['pointerdown', 'click', 'touchstart'].forEach(ev => wrap.addEventListener(ev, (e) => e.stopPropagation()));
|
|
7828
7862
|
container.appendChild(wrap);
|
|
7829
7863
|
this._composer = wrap;
|
|
@@ -7838,6 +7872,12 @@
|
|
|
7838
7872
|
const cnt = this._composer.querySelector('.nx-hist-count');
|
|
7839
7873
|
if (cnt) cnt.textContent = n ? (n > 99 ? '99+' : String(n)) : '';
|
|
7840
7874
|
this._composer.classList.add('open');
|
|
7875
|
+
clearInterval(this._subTimer);
|
|
7876
|
+
this._subTimer = setInterval(() => this._echoSubtitle(), 300);
|
|
7877
|
+
this._echoSubtitle();
|
|
7878
|
+
// The bubbles step down below the open box (both live at the top now).
|
|
7879
|
+
const host = getContainer();
|
|
7880
|
+
if (host) { host.classList.add('nx-composing'); requestAnimationFrame(() => host.style.setProperty('--nx-compose-h', (this._composer.offsetHeight || 120) + 'px')); }
|
|
7841
7881
|
this._fitKeyboard();
|
|
7842
7882
|
if (!this._vvBound && window.visualViewport) {
|
|
7843
7883
|
this._vvBound = true;
|
|
@@ -7869,9 +7909,23 @@
|
|
|
7869
7909
|
if (!this._composer) return;
|
|
7870
7910
|
if (this._composer.classList.contains('open')) UI_GUARD.shield();
|
|
7871
7911
|
this._composer.classList.remove('open');
|
|
7912
|
+
clearInterval(this._subTimer);
|
|
7913
|
+
getContainer()?.classList.remove('nx-composing');
|
|
7872
7914
|
try { this._input?.blur(); } catch (_) { }
|
|
7873
7915
|
},
|
|
7874
7916
|
isOpen() { return !!this._composer?.classList.contains('open'); },
|
|
7917
|
+
_echoSubtitle() {
|
|
7918
|
+
const box = this._composer?.querySelector('.nx-compose-sub');
|
|
7919
|
+
if (!box) return;
|
|
7920
|
+
if (!this.isOpen()) { clearInterval(this._subTimer); return; }
|
|
7921
|
+
let text = '';
|
|
7922
|
+
const finalSlot = shadow?.getElementById('nxu-final-slot');
|
|
7923
|
+
const last = finalSlot?.lastElementChild;
|
|
7924
|
+
if (last && TRANSLATOR.active) text = (last.querySelector('.sub-final-text')?.textContent || '' || '').trim();
|
|
7925
|
+
if (!text) { try { text = COVER._cueText(); } catch (_) { } }
|
|
7926
|
+
text = String(text || '').replace(/\s+/g, ' ').trim().slice(0, 160);
|
|
7927
|
+
if (box.textContent !== (text ? '💬 ' + text : '')) box.textContent = text ? '💬 ' + text : '';
|
|
7928
|
+
},
|
|
7875
7929
|
// Quick replies in the reader's language; ✏️ edits them (saved on this device).
|
|
7876
7930
|
_renderChips() {
|
|
7877
7931
|
const chips = this._composer?.querySelector('.nx-compose-chips');
|
|
@@ -8285,9 +8339,13 @@
|
|
|
8285
8339
|
_cancelledFor: '',
|
|
8286
8340
|
_count: null,
|
|
8287
8341
|
enabled() { return STORE.get('nxu_auto_next', true) !== false; },
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8342
|
+
// Words that clearly mean "next episode" …
|
|
8343
|
+
RX_TEXT: /(다음\s*(화|회|편|회차|에피소드|episode|ep\.?)|next\s*(episode|ep\b|ep\.|part|chapter)|次の(話|エピソード)|下一[集话話]|siguiente\s*episodio|épisode\s*suivant|nächste\s*folge|próximo\s*episódio)/i,
|
|
8344
|
+
// … and ones that only might ("다음", "Next", ⏭): they need a matching class/id too.
|
|
8345
|
+
RX_WEAK: /(^\s*다음\s*[▶>»]?\s*$|^\s*next\s*[▶>»]?\s*$|^\s*⏭\s*$|次へ)/i,
|
|
8346
|
+
RX_ATTR: /(next[-_ ]?(ep|episode|eps|video|chapter)|episode[-_]?next|ep[-_]?next|btn[-_]?next[-_]?ep)/i,
|
|
8347
|
+
RX_ATTR_WEAK: /(btn[-_]?next|nav[-_]?next|\bnext\b)/i,
|
|
8348
|
+
RX_BAD: /(prev|이전|previous|前|上一|comment|댓글|page|페이지|article|post|글|게시물|기사|뉴스|story|reply)/i,
|
|
8291
8349
|
_visible(el) {
|
|
8292
8350
|
if (!el.isConnected || el.closest('#nxu-host')) return false;
|
|
8293
8351
|
const r = el.getBoundingClientRect();
|
|
@@ -8303,7 +8361,7 @@
|
|
|
8303
8361
|
const here = location.origin + location.pathname + location.search;
|
|
8304
8362
|
const out = [];
|
|
8305
8363
|
const rel = document.querySelector('a[rel~="next"][href], link[rel~="next"][href]');
|
|
8306
|
-
if (rel) out.push({ el: rel.tagName === 'A' ? rel : null, href: rel.href, label: rel.tagName === 'A' ? this._label(rel) : '', score:
|
|
8364
|
+
if (rel && !this.RX_BAD.test(rel.href)) out.push({ el: rel.tagName === 'A' ? rel : null, href: rel.href, label: rel.tagName === 'A' ? this._label(rel) : '', score: 30 });
|
|
8307
8365
|
const nodes = document.querySelectorAll('a[href], button, [role="button"], [onclick]');
|
|
8308
8366
|
let n = 0;
|
|
8309
8367
|
for (const el of nodes) {
|
|
@@ -8312,9 +8370,11 @@
|
|
|
8312
8370
|
const attrs = `${el.id} ${typeof el.className === 'string' ? el.className : ''} ${el.getAttribute('data-action') || ''}`;
|
|
8313
8371
|
let score = 0;
|
|
8314
8372
|
if (this.RX_TEXT.test(label)) score += 60;
|
|
8315
|
-
if (this.
|
|
8373
|
+
else if (this.RX_WEAK.test(label)) score += 20;
|
|
8374
|
+
if (this.RX_ATTR.test(attrs)) score += 40;
|
|
8375
|
+
else if (this.RX_ATTR_WEAK.test(attrs)) score += 15;
|
|
8316
8376
|
if (!score) continue;
|
|
8317
|
-
if (this.RX_BAD.test(label) || /prev/i.test(attrs)) score -= 70;
|
|
8377
|
+
if (this.RX_BAD.test(label) || /prev|page|post|article|comment/i.test(attrs)) score -= 70;
|
|
8318
8378
|
if (score <= 0 || !this._visible(el)) continue;
|
|
8319
8379
|
const href = el.tagName === 'A' ? el.href : '';
|
|
8320
8380
|
if (href) {
|
package/package.json
CHANGED