checkpoint-cli 0.1.7 → 0.1.8

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.
Files changed (2) hide show
  1. package/dist/index.js +218 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -331,6 +331,140 @@ function trackingScript() {
331
331
  return null;
332
332
  }
333
333
 
334
+ function getVisibleText(node){
335
+ try{
336
+ if(!node||!node.textContent) return '';
337
+ return shortText(String(node.textContent).replace(/\\s+/g,' ').trim()).toLowerCase();
338
+ }catch(e){}
339
+ return '';
340
+ }
341
+
342
+ function getNavSignature(){
343
+ try{
344
+ var candidates=[];
345
+ var activeSelectors=[
346
+ '[aria-current=\"page\"]',
347
+ '[aria-selected=\"true\"]',
348
+ '[data-state=\"active\"]',
349
+ '.active',
350
+ '.is-active',
351
+ '.selected'
352
+ ];
353
+ for(var i=0;i<activeSelectors.length;i++){
354
+ var nodes=document.querySelectorAll(activeSelectors[i]);
355
+ for(var j=0;j<nodes.length&&candidates.length<8;j++){
356
+ var text=getVisibleText(nodes[j]);
357
+ if(text) candidates.push(text);
358
+ }
359
+ }
360
+ if(candidates.length===0){
361
+ var heading=document.querySelector('main h1, [role=\"main\"] h1, h1, h2');
362
+ var fallbackText=getVisibleText(heading);
363
+ if(fallbackText) candidates.push(fallbackText);
364
+ }
365
+ if(candidates.length===0) return '';
366
+ return candidates.slice(0,4).join('|');
367
+ }catch(e){}
368
+ return '';
369
+ }
370
+
371
+ function getHeadingSignature(){
372
+ try{
373
+ var heading=document.querySelector('main h1, [role=\"main\"] h1, h1, h2');
374
+ return getVisibleText(heading);
375
+ }catch(e){}
376
+ return '';
377
+ }
378
+
379
+ function getViewContext(){
380
+ return {
381
+ nav_signature:getNavSignature()||undefined,
382
+ heading_signature:getHeadingSignature()||undefined
383
+ };
384
+ }
385
+
386
+ function viewContextMatches(expected){
387
+ if(!expected||typeof expected!=='object') return true;
388
+ var current=getViewContext();
389
+ if(expected.nav_signature&&current.nav_signature&&expected.nav_signature!==current.nav_signature){
390
+ return false;
391
+ }
392
+ if(expected.heading_signature&&current.heading_signature&&expected.heading_signature!==current.heading_signature){
393
+ return false;
394
+ }
395
+ return true;
396
+ }
397
+
398
+ function isScrollable(el){
399
+ if(!el||el.nodeType!==1||!window.getComputedStyle) return false;
400
+ try{
401
+ var style=window.getComputedStyle(el);
402
+ var overflowY=String(style.overflowY||'').toLowerCase();
403
+ var overflowX=String(style.overflowX||'').toLowerCase();
404
+ var canScrollY=(overflowY==='auto'||overflowY==='scroll'||overflowY==='overlay')&&(el.scrollHeight-el.clientHeight>1);
405
+ var canScrollX=(overflowX==='auto'||overflowX==='scroll'||overflowX==='overlay')&&(el.scrollWidth-el.clientWidth>1);
406
+ return !!(canScrollX||canScrollY);
407
+ }catch(e){}
408
+ return false;
409
+ }
410
+
411
+ function getNearestScrollContainer(el){
412
+ if(!el) return null;
413
+ var current=el.parentElement;
414
+ var depth=0;
415
+ while(current&&depth<10){
416
+ if(current===document.body||current===document.documentElement) return null;
417
+ if(isScrollable(current)) return current;
418
+ current=current.parentElement;
419
+ depth++;
420
+ }
421
+ return null;
422
+ }
423
+
424
+ function getScrollContext(el,clientX,clientY){
425
+ var container=getNearestScrollContainer(el);
426
+ if(!container) return null;
427
+ var rect=container.getBoundingClientRect?container.getBoundingClientRect():null;
428
+ if(!rect) return null;
429
+ var safeW=Math.max(1,rect.width||1);
430
+ var safeH=Math.max(1,rect.height||1);
431
+ return {
432
+ container_selector_chain:getSelectorChain(container),
433
+ container_fingerprint:getDomFingerprint(container),
434
+ container_x_percent:clampPercent(((clientX-rect.left)/safeW)*100),
435
+ container_y_percent:clampPercent(((clientY-rect.top)/safeH)*100)
436
+ };
437
+ }
438
+
439
+ function getPointFromScrollContext(scrollContext){
440
+ if(!scrollContext||typeof scrollContext!=='object') return null;
441
+ var container=null;
442
+ if(Array.isArray(scrollContext.container_selector_chain)){
443
+ for(var i=0;i<scrollContext.container_selector_chain.length;i++){
444
+ var candidate=scrollContext.container_selector_chain[i];
445
+ if(!candidate||typeof candidate.selector!=='string') continue;
446
+ var nodes=querySelectorAllSafe(document,candidate.selector);
447
+ var match=pickBestElement(nodes,{coord_fallback:null},getMetrics(),null);
448
+ if(match&&match.element){
449
+ container=match.element;
450
+ break;
451
+ }
452
+ }
453
+ }
454
+ if(!container&&scrollContext.container_fingerprint){
455
+ var fp=resolveFromFingerprint(scrollContext.container_fingerprint,{coord_fallback:null},getMetrics(),document);
456
+ container=fp&&fp.element?fp.element:null;
457
+ }
458
+ if(!container||!container.getBoundingClientRect) return null;
459
+ var rect=container.getBoundingClientRect();
460
+ var relX=typeof scrollContext.container_x_percent==='number'?clampPercent(scrollContext.container_x_percent)/100:0.5;
461
+ var relY=typeof scrollContext.container_y_percent==='number'?clampPercent(scrollContext.container_y_percent)/100:0.5;
462
+ return {
463
+ clientX:rect.left+(rect.width*relX),
464
+ clientY:rect.top+(rect.height*relY)
465
+ };
466
+ }
467
+
334
468
  function buildCoordFallback(clientX,clientY,metrics){
335
469
  var safeViewW=metrics.viewWidth||1;
336
470
  var safeViewH=metrics.viewHeight||1;
@@ -377,6 +511,8 @@ function trackingScript() {
377
511
  },
378
512
  container_hint:getContainerHint(el),
379
513
  transient_context:getTransientContext(el),
514
+ view_context:getViewContext(),
515
+ scroll_context:getScrollContext(el,clientX,clientY),
380
516
  coord_fallback:fallback
381
517
  };
382
518
  }
@@ -452,13 +588,16 @@ function trackingScript() {
452
588
  }
453
589
 
454
590
  function pickBestElement(nodes,payload,metrics,fingerprintPath){
455
- if(!nodes||nodes.length===0) return null;
591
+ if(!nodes||nodes.length===0) return { element:null, ambiguous:false, bestScore:Infinity };
456
592
  var target=getTargetDocPoint(payload,metrics);
457
593
  var best=null;
458
594
  var bestScore=Infinity;
595
+ var secondScore=Infinity;
596
+ var matchCount=0;
459
597
  for(var i=0;i<nodes.length;i++){
460
598
  var node=nodes[i];
461
599
  if(!node||node.nodeType!==1) continue;
600
+ matchCount++;
462
601
  var anchorPoint=getAnchorClientPoint(node,payload);
463
602
  var docX=anchorPoint.clientX+metrics.scrollX;
464
603
  var docY=anchorPoint.clientY+metrics.scrollY;
@@ -472,11 +611,23 @@ function trackingScript() {
472
611
  score+=siblingPathDistance(node,fingerprintPath)*40;
473
612
  }
474
613
  if(score<bestScore){
614
+ secondScore=bestScore;
475
615
  best=node;
476
616
  bestScore=score;
617
+ }else if(score<secondScore){
618
+ secondScore=score;
477
619
  }
478
620
  }
479
- return best;
621
+ if(!best) return { element:null, ambiguous:false, bestScore:Infinity };
622
+ var ambiguous=false;
623
+ if(matchCount>1){
624
+ if(!target){
625
+ ambiguous=true;
626
+ }else if((secondScore-bestScore)<18){
627
+ ambiguous=true;
628
+ }
629
+ }
630
+ return { element:best, ambiguous:ambiguous, bestScore:bestScore };
480
631
  }
481
632
 
482
633
  function querySelectorAllSafe(scope,selector){
@@ -489,14 +640,14 @@ function trackingScript() {
489
640
  }
490
641
 
491
642
  function resolveFromFingerprint(fingerprint,payload,metrics,scope){
492
- if(!fingerprint) return null;
643
+ if(!fingerprint) return { element:null, ambiguous:false, bestScore:Infinity };
493
644
  var searchScope=scope||document;
494
645
  if(fingerprint.id){
495
646
  var byId=searchScope.getElementById?searchScope.getElementById(fingerprint.id):null;
496
647
  if(!byId&&searchScope.querySelector){
497
648
  try{ byId=searchScope.querySelector('#'+cssEscape(fingerprint.id)); }catch(e){}
498
649
  }
499
- if(byId) return byId;
650
+ if(byId) return { element:byId, ambiguous:false, bestScore:0 };
500
651
  }
501
652
  var tag=fingerprint.tag||'*';
502
653
  var selector=tag;
@@ -527,20 +678,30 @@ function trackingScript() {
527
678
  var candidate=transient.root_selector_chain[i];
528
679
  if(!candidate||typeof candidate.selector!=='string') continue;
529
680
  var nodes=querySelectorAllSafe(document,candidate.selector);
530
- root=pickBestElement(nodes,payload,metrics,transient.root_fingerprint&&transient.root_fingerprint.sibling_path);
681
+ var rootMatch=pickBestElement(nodes,payload,metrics,transient.root_fingerprint&&transient.root_fingerprint.sibling_path);
682
+ root=rootMatch.element;
531
683
  if(root) return root;
532
684
  }
533
685
  }
534
- root=resolveFromFingerprint(transient.root_fingerprint,payload,metrics,document);
686
+ root=resolveFromFingerprint(transient.root_fingerprint,payload,metrics,document).element;
535
687
  return root;
536
688
  }
537
689
 
538
690
  function resolveAnchor(payload){
539
691
  if(!payload||typeof payload!=='object') return {coordFallback:null,strategy:'none',matchedSelector:null,status:'none'};
692
+ if(!viewContextMatches(payload.view_context)){
693
+ return {
694
+ coordFallback:payload.coord_fallback||null,
695
+ strategy:'context_mismatch',
696
+ matchedSelector:null,
697
+ status:'context_mismatch'
698
+ };
699
+ }
540
700
  var element=null;
541
701
  var strategy='none';
542
702
  var matchedSelector=null;
543
703
  var metrics=getMetrics();
704
+ var ambiguous=false;
544
705
  var transientRoot=null;
545
706
  var hasTransientContext=!!(payload.transient_context&&typeof payload.transient_context==='object');
546
707
  if(hasTransientContext){
@@ -557,8 +718,9 @@ function trackingScript() {
557
718
  if(!candidate||typeof candidate.selector!=='string') continue;
558
719
  var nodes=querySelectorAllSafe(scope,candidate.selector);
559
720
  var found=pickBestElement(nodes,payload,metrics,null);
560
- if(found){
561
- element=found;
721
+ if(found&&found.element){
722
+ element=found.element;
723
+ ambiguous=!!found.ambiguous;
562
724
  strategy='selector';
563
725
  matchedSelector=(scopeTag?scopeTag+': ':'')+candidate.selector;
564
726
  return true;
@@ -581,28 +743,64 @@ function trackingScript() {
581
743
  isStrongContainerHint(payload.container_hint)
582
744
  ){
583
745
  var containerNodesInRoot=transientRoot?querySelectorAllSafe(transientRoot,payload.container_hint.selector):[];
584
- var containerEl=pickBestElement(containerNodesInRoot,payload,metrics,null);
585
- if(!containerEl){
746
+ var containerMatch=pickBestElement(containerNodesInRoot,payload,metrics,null);
747
+ if(!containerMatch.element){
586
748
  var containerNodes=querySelectorAllSafe(document,payload.container_hint.selector);
587
- containerEl=pickBestElement(containerNodes,payload,metrics,null);
749
+ containerMatch=pickBestElement(containerNodes,payload,metrics,null);
588
750
  }
589
- if(containerEl){
590
- element=containerEl;
751
+ if(containerMatch.element){
752
+ element=containerMatch.element;
753
+ ambiguous=!!containerMatch.ambiguous;
591
754
  strategy='container';
592
755
  matchedSelector=payload.container_hint.selector;
593
756
  }
594
757
  }
595
758
  if(!element){
596
- var fpEl=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,transientRoot||undefined);
597
- if(!fpEl){
598
- fpEl=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,document);
759
+ var fpMatch=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,transientRoot||undefined);
760
+ if(!fpMatch.element){
761
+ fpMatch=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,document);
599
762
  }
600
- if(fpEl){
601
- element=fpEl;
763
+ if(fpMatch.element){
764
+ element=fpMatch.element;
765
+ ambiguous=!!fpMatch.ambiguous;
602
766
  strategy='fingerprint';
603
767
  }
604
768
  }
769
+ if(!element&&payload.scroll_context){
770
+ var fromContainer=getPointFromScrollContext(payload.scroll_context);
771
+ if(fromContainer){
772
+ return {
773
+ coordFallback:buildCoordFallback(fromContainer.clientX,fromContainer.clientY,metrics),
774
+ strategy:'scroll_container',
775
+ matchedSelector:null,
776
+ status:'resolved'
777
+ };
778
+ }
779
+ }
605
780
  if(!element) return {coordFallback:null,strategy:'fallback',matchedSelector:null,status:'fallback_only'};
781
+ if(ambiguous){
782
+ return {
783
+ coordFallback:payload.coord_fallback||null,
784
+ strategy:'ambiguous',
785
+ matchedSelector:matchedSelector,
786
+ status:'ambiguous'
787
+ };
788
+ }
789
+ var target=getTargetDocPoint(payload,metrics);
790
+ if(target){
791
+ var anchorPoint=getAnchorClientPoint(element,payload);
792
+ var distX=(anchorPoint.clientX+metrics.scrollX)-target.x;
793
+ var distY=(anchorPoint.clientY+metrics.scrollY)-target.y;
794
+ var dist=Math.sqrt(distX*distX+distY*distY);
795
+ if(dist>220){
796
+ return {
797
+ coordFallback:payload.coord_fallback||null,
798
+ strategy:'ambiguous',
799
+ matchedSelector:matchedSelector,
800
+ status:'ambiguous'
801
+ };
802
+ }
803
+ }
606
804
  var point=getAnchorClientPoint(element,payload);
607
805
  var clientX=point.clientX;
608
806
  var clientY=point.clientY;
@@ -695,6 +893,7 @@ function trackingScript() {
695
893
  scrollTick=true;
696
894
  requestAnimationFrame(function(){
697
895
  reportScroll();
896
+ reportDomMutation('scroll');
698
897
  scrollTick=false;
699
898
  });
700
899
  }
@@ -758,6 +957,7 @@ function trackingScript() {
758
957
  window.addEventListener('popstate',reportNav);
759
958
  window.addEventListener('hashchange',reportNav);
760
959
  window.addEventListener('scroll',onScroll,{passive:true});
960
+ document.addEventListener('scroll',onScroll,true);
761
961
  window.addEventListener('resize',reportScroll);
762
962
  window.addEventListener('message',function(e){
763
963
  try{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "checkpoint-cli",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Share your localhost with reviewers — get visual feedback directly on the page",
5
5
  "keywords": [
6
6
  "checkpoint",