checkpoint-cli 0.1.7 → 0.1.9

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 +238 -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
  }
@@ -451,14 +587,35 @@ function trackingScript() {
451
587
  return dist;
452
588
  }
453
589
 
590
+ function textMatchScore(node,payload){
591
+ if(!payload||!payload.text_context) return 0;
592
+ var tc=payload.text_context;
593
+ var selfText=getVisibleText(node);
594
+ var parentText=node.parentElement?getVisibleText(node.parentElement):'';
595
+ var bonus=0;
596
+ if(tc.self&&selfText&&tc.self.length>2){
597
+ if(selfText===tc.self.toLowerCase()) bonus+=200;
598
+ else if(selfText.indexOf(tc.self.toLowerCase())>=0||tc.self.toLowerCase().indexOf(selfText)>=0) bonus+=80;
599
+ }
600
+ if(tc.parent&&parentText&&tc.parent.length>2){
601
+ if(parentText===tc.parent.toLowerCase()) bonus+=100;
602
+ else if(parentText.indexOf(tc.parent.toLowerCase())>=0||tc.parent.toLowerCase().indexOf(parentText)>=0) bonus+=40;
603
+ }
604
+ return bonus;
605
+ }
606
+
454
607
  function pickBestElement(nodes,payload,metrics,fingerprintPath){
455
- if(!nodes||nodes.length===0) return null;
608
+ if(!nodes||nodes.length===0) return { element:null, ambiguous:false, bestScore:Infinity };
456
609
  var target=getTargetDocPoint(payload,metrics);
457
610
  var best=null;
458
611
  var bestScore=Infinity;
612
+ var secondScore=Infinity;
613
+ var matchCount=0;
614
+ var bestTextBonus=0;
459
615
  for(var i=0;i<nodes.length;i++){
460
616
  var node=nodes[i];
461
617
  if(!node||node.nodeType!==1) continue;
618
+ matchCount++;
462
619
  var anchorPoint=getAnchorClientPoint(node,payload);
463
620
  var docX=anchorPoint.clientX+metrics.scrollX;
464
621
  var docY=anchorPoint.clientY+metrics.scrollY;
@@ -471,12 +628,27 @@ function trackingScript() {
471
628
  if(Array.isArray(fingerprintPath)&&fingerprintPath.length>0){
472
629
  score+=siblingPathDistance(node,fingerprintPath)*40;
473
630
  }
631
+ var tBonus=textMatchScore(node,payload);
632
+ score=Math.max(0,score-tBonus);
474
633
  if(score<bestScore){
634
+ secondScore=bestScore;
475
635
  best=node;
476
636
  bestScore=score;
637
+ bestTextBonus=tBonus;
638
+ }else if(score<secondScore){
639
+ secondScore=score;
640
+ }
641
+ }
642
+ if(!best) return { element:null, ambiguous:false, bestScore:Infinity };
643
+ var ambiguous=false;
644
+ if(matchCount>1){
645
+ if(!target&&bestTextBonus===0){
646
+ ambiguous=true;
647
+ }else if((secondScore-bestScore)<30&&bestTextBonus<80){
648
+ ambiguous=true;
477
649
  }
478
650
  }
479
- return best;
651
+ return { element:best, ambiguous:ambiguous, bestScore:bestScore };
480
652
  }
481
653
 
482
654
  function querySelectorAllSafe(scope,selector){
@@ -489,14 +661,14 @@ function trackingScript() {
489
661
  }
490
662
 
491
663
  function resolveFromFingerprint(fingerprint,payload,metrics,scope){
492
- if(!fingerprint) return null;
664
+ if(!fingerprint) return { element:null, ambiguous:false, bestScore:Infinity };
493
665
  var searchScope=scope||document;
494
666
  if(fingerprint.id){
495
667
  var byId=searchScope.getElementById?searchScope.getElementById(fingerprint.id):null;
496
668
  if(!byId&&searchScope.querySelector){
497
669
  try{ byId=searchScope.querySelector('#'+cssEscape(fingerprint.id)); }catch(e){}
498
670
  }
499
- if(byId) return byId;
671
+ if(byId) return { element:byId, ambiguous:false, bestScore:0 };
500
672
  }
501
673
  var tag=fingerprint.tag||'*';
502
674
  var selector=tag;
@@ -527,20 +699,30 @@ function trackingScript() {
527
699
  var candidate=transient.root_selector_chain[i];
528
700
  if(!candidate||typeof candidate.selector!=='string') continue;
529
701
  var nodes=querySelectorAllSafe(document,candidate.selector);
530
- root=pickBestElement(nodes,payload,metrics,transient.root_fingerprint&&transient.root_fingerprint.sibling_path);
702
+ var rootMatch=pickBestElement(nodes,payload,metrics,transient.root_fingerprint&&transient.root_fingerprint.sibling_path);
703
+ root=rootMatch.element;
531
704
  if(root) return root;
532
705
  }
533
706
  }
534
- root=resolveFromFingerprint(transient.root_fingerprint,payload,metrics,document);
707
+ root=resolveFromFingerprint(transient.root_fingerprint,payload,metrics,document).element;
535
708
  return root;
536
709
  }
537
710
 
538
711
  function resolveAnchor(payload){
539
712
  if(!payload||typeof payload!=='object') return {coordFallback:null,strategy:'none',matchedSelector:null,status:'none'};
713
+ if(!viewContextMatches(payload.view_context)){
714
+ return {
715
+ coordFallback:payload.coord_fallback||null,
716
+ strategy:'context_mismatch',
717
+ matchedSelector:null,
718
+ status:'context_mismatch'
719
+ };
720
+ }
540
721
  var element=null;
541
722
  var strategy='none';
542
723
  var matchedSelector=null;
543
724
  var metrics=getMetrics();
725
+ var ambiguous=false;
544
726
  var transientRoot=null;
545
727
  var hasTransientContext=!!(payload.transient_context&&typeof payload.transient_context==='object');
546
728
  if(hasTransientContext){
@@ -557,8 +739,9 @@ function trackingScript() {
557
739
  if(!candidate||typeof candidate.selector!=='string') continue;
558
740
  var nodes=querySelectorAllSafe(scope,candidate.selector);
559
741
  var found=pickBestElement(nodes,payload,metrics,null);
560
- if(found){
561
- element=found;
742
+ if(found&&found.element){
743
+ element=found.element;
744
+ ambiguous=!!found.ambiguous;
562
745
  strategy='selector';
563
746
  matchedSelector=(scopeTag?scopeTag+': ':'')+candidate.selector;
564
747
  return true;
@@ -581,28 +764,64 @@ function trackingScript() {
581
764
  isStrongContainerHint(payload.container_hint)
582
765
  ){
583
766
  var containerNodesInRoot=transientRoot?querySelectorAllSafe(transientRoot,payload.container_hint.selector):[];
584
- var containerEl=pickBestElement(containerNodesInRoot,payload,metrics,null);
585
- if(!containerEl){
767
+ var containerMatch=pickBestElement(containerNodesInRoot,payload,metrics,null);
768
+ if(!containerMatch.element){
586
769
  var containerNodes=querySelectorAllSafe(document,payload.container_hint.selector);
587
- containerEl=pickBestElement(containerNodes,payload,metrics,null);
770
+ containerMatch=pickBestElement(containerNodes,payload,metrics,null);
588
771
  }
589
- if(containerEl){
590
- element=containerEl;
772
+ if(containerMatch.element){
773
+ element=containerMatch.element;
774
+ ambiguous=!!containerMatch.ambiguous;
591
775
  strategy='container';
592
776
  matchedSelector=payload.container_hint.selector;
593
777
  }
594
778
  }
595
779
  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);
780
+ var fpMatch=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,transientRoot||undefined);
781
+ if(!fpMatch.element){
782
+ fpMatch=resolveFromFingerprint(payload.dom_fingerprint,payload,metrics,document);
599
783
  }
600
- if(fpEl){
601
- element=fpEl;
784
+ if(fpMatch.element){
785
+ element=fpMatch.element;
786
+ ambiguous=!!fpMatch.ambiguous;
602
787
  strategy='fingerprint';
603
788
  }
604
789
  }
790
+ if(!element&&payload.scroll_context){
791
+ var fromContainer=getPointFromScrollContext(payload.scroll_context);
792
+ if(fromContainer){
793
+ return {
794
+ coordFallback:buildCoordFallback(fromContainer.clientX,fromContainer.clientY,metrics),
795
+ strategy:'scroll_container',
796
+ matchedSelector:null,
797
+ status:'resolved'
798
+ };
799
+ }
800
+ }
605
801
  if(!element) return {coordFallback:null,strategy:'fallback',matchedSelector:null,status:'fallback_only'};
802
+ if(ambiguous){
803
+ return {
804
+ coordFallback:payload.coord_fallback||null,
805
+ strategy:'ambiguous',
806
+ matchedSelector:matchedSelector,
807
+ status:'ambiguous'
808
+ };
809
+ }
810
+ var target=getTargetDocPoint(payload,metrics);
811
+ if(target){
812
+ var anchorPoint=getAnchorClientPoint(element,payload);
813
+ var distX=(anchorPoint.clientX+metrics.scrollX)-target.x;
814
+ var distY=(anchorPoint.clientY+metrics.scrollY)-target.y;
815
+ var dist=Math.sqrt(distX*distX+distY*distY);
816
+ if(dist>220){
817
+ return {
818
+ coordFallback:payload.coord_fallback||null,
819
+ strategy:'ambiguous',
820
+ matchedSelector:matchedSelector,
821
+ status:'ambiguous'
822
+ };
823
+ }
824
+ }
606
825
  var point=getAnchorClientPoint(element,payload);
607
826
  var clientX=point.clientX;
608
827
  var clientY=point.clientY;
@@ -758,6 +977,7 @@ function trackingScript() {
758
977
  window.addEventListener('popstate',reportNav);
759
978
  window.addEventListener('hashchange',reportNav);
760
979
  window.addEventListener('scroll',onScroll,{passive:true});
980
+ document.addEventListener('scroll',onScroll,true);
761
981
  window.addEventListener('resize',reportScroll);
762
982
  window.addEventListener('message',function(e){
763
983
  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.9",
4
4
  "description": "Share your localhost with reviewers — get visual feedback directly on the page",
5
5
  "keywords": [
6
6
  "checkpoint",