1/*jslint browser:true,multivar:true */ /*** UWAGA, sprawdzaj kod na stronie http://jslint.com ***/ 2/*global gapi,this,$,window,EMPIKFOTO,FB */ 3if (!window.hasOwnProperty('EMPIKFOTO')) { 4 window.EMPIKFOTO = {}; 5} 6 7//<editor-fold defaultstate="expanded" desc="EMPIKFOTO.socialServiceFb"> 8EMPIKFOTO.socialServiceFb = function (spec, my) { 9 "use strict"; 10 11 if (typeof EMPIKFOTO.socialService !== 'function') { 12 if (typeof window !== 'undefined' && window.console && typeof window.console.warn === 'function') { 13 window.console.warn('EMPIKFOTO.socialServiceFb: brak EMPIKFOTO.socialService â inicjalizacja pominiÄta.'); 14 } 15 return null; 16 } 17 18 //<editor-fold defaultstate="collapsed" desc="zmienne i inicjalizacja"> 19 var that = EMPIKFOTO.socialService(spec, my), 20 21 // ['public_profile', 'email', 'user_photos', 'user_friends', 'friends_photos'], 22 permissions = ['public_profile', 'email', 'user_photos'], 23 permissionsDesc = { 24 'public_profile': 'fbPermissionPublicProfile', 25 'email': 'fbPermissionEmail', 26 'user_photos': 'fbPermissionUserPhotos' 27 }, 28 29 deferScripts = new $.Deferred(), 30 deferAsyncInit = new $.Deferred(), 31 deferService = new $.Deferred(), 32 33 afterInit = [], 34 inited = false, 35 // W zmiennej error pojawi siÄ bÅÄ d, jeżeli wystÄ pi podczas inicjalizacji 36 // Obiekt bÅÄdu: {id: 'fbLoginError', msg: 'WystÄ piÅ problem z logowaniem.', resolve: that.login()} 37 initError = false, 38 39 init, 40 login, 41 loginAfterRegister, 42 frontLogin, 43 frontRegister, 44 isConnected, 45 isAlbumList, 46 hasSearcher, 47 setSearchParams, 48 getMediaCount, 49 getUserAlbums, 50 getPhotosForAlbumId, 51 getPhotos, 52 getAlbums, 53 getName, 54 errorDesc, 55 isEditorEnvironment, 56 57 setLoginContainer; 58 59 my = my || {}; 60 //</editor-fold> 61 62 //<editor-fold defaultstate="collapsed" desc="init"> 63 init = function () { 64 65 deferScripts = new $.Deferred(); 66 deferAsyncInit = new $.Deferred(); 67 deferService = new $.Deferred(); 68 69 inited = false; 70 initError = false; 71 afterInit = []; 72 73 // Utworzenie elementu potrzebnego skryptom fb 74 $('script#facebook-jssdk').remove(); 75 $('body').remove('#fb-root').append('<div id="fb-root"></div>'); 76 77 // Funkcja inicjujÄ ca (niestety globalna) 78 window.fbAsyncInit = function () { 79 FB.init({ 80 appId: EMPIKFOTO.config.facebook_app_id, 81 status: true, 82 cookie: true, 83 xfbml: false, 84 version: 'v3.2' 85 }); 86 deferAsyncInit.resolve(); 87 }; 88 89 // Asynchroniczne zaÅadowanie skryptów z fb 90 (function (d, s, id) { 91 var js, fjs = d.getElementsByTagName(s)[0]; 92 if (d.getElementById(id)) { 93 deferScripts.reject(EMPIKFOTO.statement().error().icon().id('fbNoRootElement') 94 .context(that.getName()) 95 .msg(EMPIKFOTO.localize.get('fbNoRootElement')) 96 .btn(EMPIKFOTO.localize.get('fbTryAgain'), function () { 97 init(); 98 })); 99 return; 100 } 101 js = d.createElement(s); 102 js.id = id; 103 js.src = EMPIKFOTO.socialMedia.facebook.srcUrl; 104 js.onerror = function () { 105 deferScripts.reject(EMPIKFOTO.statement().error().icon().id('fbLoadingProblem') 106 .context(that.getName()) 107 .msg(EMPIKFOTO.localize.get('fbLoadingProblem')) 108 .btn(EMPIKFOTO.localize.get('fbTryAgain'), function () { 109 init(); 110 })); 111 }; 112 // http://stackoverflow.com/questions/538745/how-to-tell-if-a-script-tag-failed-to-load 113 js.onload = js.onreadystatechange = function () { 114 if (!this.readyState || this.readyState === 'loaded' || this.readyState === 'complete') { 115 deferScripts.resolve(); 116 } 117 }; 118 fjs.parentNode.insertBefore(js, fjs); 119 }(document, 'script', 'facebook-jssdk')); 120 121 EMPIKFOTO.socialServiceFb = that; 122 deferService.resolve(); 123 124 $.when(deferScripts, deferAsyncInit, deferService). 125 done(function () { 126 var toRun; 127 if (!initError) { 128 inited = true; 129 } 130 131 toRun = afterInit.shift(); 132 while ('function' === typeof toRun) { 133 toRun(); 134 toRun = afterInit.shift(); 135 } 136 }). 137 fail(function (error) { 138 initError = error; 139 }); 140 }; 141 that.init = init; 142 //</editor-fold> 143 144 //<editor-fold defaultstate="collapsed" desc="isEditorEnvironment() - zwraca informacjÄ o tym czy jest to Årodowisko edytora"> 145 isEditorEnvironment = function () { 146 return window.hasOwnProperty('editorApi'); 147 }; 148 //</editor-fold> 149 150 //<editor-fold defaultstate="collapsed" desc="login"> 151 // Metoda wyÅwietla komunikaty w gÅównym messengerze. Po logowaniu nastÄpuje 152 // przejÅcie pod adres do utworzenia sesji w po stronei serwera PHP 153 login = function (onSuccess, onError, onLoginNotExist) { 154 if (!inited) { 155 afterInit.push(function () { 156 login(onSuccess, onError); 157 }); 158 return true; 159 } 160 FB.login(function (permissionsResponse) { 161 if (permissionsResponse.authResponse) { 162 isConnected(function() { 163 var data = {token: permissionsResponse['authResponse']['accessToken'], type:'facebook'}, 164 isFinishBackendLogin = new $.Deferred(); 165 166 if (isEditorEnvironment()) { 167 data.empikToken = window.editorApi.getUserToken();
168 data.backupToken = window.editorApi.getStorageToken(); 169 isFinishBackendLogin.done(function () { 170 onSuccess(200); 171 }); 172 $('body').one('empikLoggedIn', function () { 173 isFinishBackendLogin.resolve(); 174 }); 175 } else { 176 isFinishBackendLogin.resolve(); 177 } 178 179 $.ajax({ 180 url: EMPIKFOTO.routing('portal_login_social'), 181 method: 'POST', 182 dataType: 'json', 183 data: data, 184 success: function (data, textStatus, jqXHR) { 185 if (data.success) { 186 187 if (isEditorEnvironment()) { 188 // Årodowisko edytora 189 190 // Po tym zdarzeniu bÄdzie wywoÅane zdarzenie 'empikLoggedIn' na 'body', dziÄki czemu wykonane zostanie onSuccess(true) 191 $('body').trigger('loginPanel.changedRole', [{role: 'member', storageToken: data.storageToken}]); 192 193 } else { 194 //OdÅwieżenie nagÅówka z informacja o loginie i zawartoÅci koszyka 195 $('body').trigger('headerInfo.update'); 196 $('body').trigger('sign-up-by-facebook'); 197 198 EMPIKFOTO.isGranted = true; 199 EMPIKFOTO.token = data.token; 200 EMPIKFOTO.backupToken = data.storageToken; 201 // User ma konto trzeba sprawdziÄ i udaÅo siÄ zaÅożyÄ sesjÄ 202 isConnected( 203 function () { 204 onSuccess(jqXHR.status); 205 }, 206 function () { 207 onError(jqXHR.status); 208 } 209 ); 210 } 211 212 } else { 213 // Problem z komunikacjÄ z fb w backendzie 214 if ('function' === typeof onError) { 215 onError(jqXHR.status); 216 } 217 } 218 }, 219 error: function (jqXHR, textStatus, errorThrown) { 220 var data = JSON.parse(jqXHR.responseText); 221 if (data.code === 404) { 222 if ('function' === typeof onLoginNotExist) { 223 onLoginNotExist(); 224 } 225 } else { 226 if ('function' === typeof onError) { 227 onError(data.message); 228 } 229 } 230 } 231 }); 232 }); 233 }else{ 234 onError(); 235 } 236 }, {scope: permissions.toString(), return_scopes: true}); 237 }; 238 that.login = login; 239 //</editor-fold> 240 241 //<editor-fold defaultstate="collapsed" desc="loginAfterRegister"> 242 loginAfterRegister = function (onSuccess, onError, onLoginNotExist) { 243 if (!inited) { 244 afterInit.push(function () { 245 loginAfterRegister(onSuccess, onError); 246 }); 247 return true; 248 } 249 isConnected(function() { 250 var data = {loginAfterRegister: '1', type: 'facebook'}, 251 isFinishBackendLogin = new $.Deferred(); 252 253 if (isEditorEnvironment()) { 254 data.empikToken = window.editorApi.getUserToken();
255 data.backupToken = window.editorApi.getStorageToken(); 256 isFinishBackendLogin.done(function () { 257 onSuccess(200); 258 }); 259 $('body').one('empikLoggedIn', function () { 260 isFinishBackendLogin.resolve(); 261 }); 262 } else { 263 isFinishBackendLogin.resolve(); 264 } 265 266 $.ajax({ 267 url: EMPIKFOTO.routing('portal_login_social'), 268 method: 'POST', 269 dataType: 'json', 270 data: data, 271 success: function (data, textStatus, jqXHR) { 272 if (data.success) { 273 274 if (isEditorEnvironment()) { 275 // Årodowisko edytora 276 277 // Po tym zdarzeniu bÄdzie wywoÅane zdarzenie 'empikLoggedIn' na 'body', dziÄki czemu wykonane zostanie onSuccess(true) 278 $('body').trigger('loginPanel.changedRole', [{role: 'member', storageToken: data.storageToken}]); 279 280 } else { 281 //OdÅwieżenie nagÅówka z informacja o loginie i zawartoÅci koszyka 282 $('body').trigger('headerInfo.update'); 283 284 EMPIKFOTO.isGranted = true; 285 EMPIKFOTO.token = data.token; 286 EMPIKFOTO.backupToken = data.storageToken; 287 // User ma konto trzeba sprawdziÄ i udaÅo siÄ zaÅożyÄ sesjÄ 288 isConnected( 289 function () { 290 onSuccess(jqXHR.status); 291 }, 292 function () { 293 onError(jqXHR.status); 294 } 295 ); 296 } 297 298 } else { 299 // Problem z komunikacjÄ z fb w backendzie 300 if ('function' === typeof onError) { 301 onError(jqXHR.status); 302 } 303 } 304 }, 305 error: function (jqXHR, textStatus, errorThrown) { 306 var data = JSON.parse(jqXHR.responseText); 307 if (data.code === 404) { 308 if ('function' === typeof onLoginNotExist) { 309 onLoginNotExist(); 310 } 311 } else { 312 if ('function' === typeof onError) { 313 onError(data.message); 314 } 315 } 316 } 317 }); 318 }); 319 }; 320 that.loginAfterRegister = loginAfterRegister; 321 //</editor-fold> 322 323 //<editor-fold defaultstate="collapsed" desc="frontLogin"> 324 frontLogin = function (onSuccess, onError) { 325 if (!inited) { 326 afterInit.push(function () { 327 frontLogin(onSuccess, onError); 328 }); 329 return true; 330 } 331 // Czy ktoÅ już jest zalogowany 332 isConnected(function () { 333 // Jest już poprawnie zalogowany i mamy wszystkie potrzebne uprawnienia 334 onSuccess(); 335 }, function () { 336 // Nie ma logowania, próba zalogowania 337 FB.login(function () { 338 // Ponowne sprawdzenie czy użytkownik siÄ zalogowaÅ 339 isConnected(onSuccess, onError); 340 }, {scope: permissions.toString(), return_scopes: true}); 341 }); 342 }; 343 that.frontLogin = frontLogin; 344 //</editor-fold> 345 346 //<editor-fold defaultstate="collapsed" desc="frontRegister"> 347 frontRegister = function (onSuccess, onError, onExists, data) { 348 if (!inited) { 349 afterInit.push(function () { 350 frontRegister(onSuccess, onError, onExists); 351 }); 352 return true; 353 } 354 $.ajax({ 355 url: EMPIKFOTO.routing('portal_api_facebook_register'), 356 method: 'POST', 357 dataType: 'json', 358 data: data, 359 success: function (data) { 360 if (data.success) { 361 onSuccess(); 362 }else{ 363 if ('function' === typeof onError) { 364 onError(); 365 } 366 } 367 }, 368 error: function (result) { 369 var data = result.responseJSON; 370 if (data.statusCode === 403 && 'function' === typeof onExists) { 371 onExists(); 372 }else if ('function' === typeof onError) { 373 onError(); 374 } 375 } 376 }); 377 }; 378 that.frontRegister = frontRegister; 379 //</editor-fold> 380 381 //<editor-fold defaultstate="collapsed" desc="isConnected"> 382 isConnected = function (onSuccess, onError) { 383 if (initError) { 384 onError(initError); 385 return false;
386 } 387 if (!inited) { 388 afterInit.push(function () { 389 isConnected(onSuccess, onError); 390 }); 391 return true; 392 } 393 FB.getLoginStatus(function (loginResponse) { 394 if ('connected' === loginResponse.status) { 395 // Użytkownik jest zalogowany 396 FB.api('/me/permissions', function (permissionsResponse) { 397 var i, result = true, notGranted = []; 398 399 if (!permissionsResponse || !permissionsResponse.data) { 400 errorDesc(permissionsResponse); 401 if ('function' === typeof onError) { 402 onError(EMPIKFOTO.statement().error().id('fbCommunicationError') 403 .context(that.getName()) 404 .msg(EMPIKFOTO.localize.get('fbCommunicationError')) 405 .btn(EMPIKFOTO.localize.get('fbTryAgain'), function () { 406 isConnected(onSuccess, onError); 407 })); 408 } 409 return; 410 } 411 for (i in permissionsResponse.data) { 412 if (permissionsResponse.data.hasOwnProperty(i) && 413 permissionsResponse.data[i].hasOwnProperty('status')) { 414 if ('granted' !== permissionsResponse.data[i].status) { 415 notGranted.push(EMPIKFOTO.localize.get(permissionsDesc[permissionsResponse.data[i].permission])); 416 result = false;
417 } 418 } 419 } 420 if (result) { 421 // Wszystko gra 422 if ('function' === typeof onSuccess) { 423 onSuccess(); 424 } 425 } else { 426 if ('function' === typeof onError) { 427 onError(EMPIKFOTO.statement().error().id('fbPermissionError') 428 .context(that.getName()) 429 .msg(EMPIKFOTO.localize.get('fbPermissionError', notGranted.join(', '))) 430 .btn(EMPIKFOTO.localize.get('fbTryAgain'), function () { 431 isConnected(onSuccess, onError); 432 })); 433 } 434 } 435 }); 436 } else if (loginResponse.status === 'not_authorized') { 437 // Aplikacja nie zostaÅa autoryzowana 438 onError(EMPIKFOTO.statement().info().id('fbConnectMessage') 439 .context(that.getName()) 440 .msg(EMPIKFOTO.localize.get('fbConnectMessagePreface'), false) 441 .msg(EMPIKFOTO.localize.get('fbConnectMessage')) 442 .btn(EMPIKFOTO.localize.get('fbConnectButton'), function () { 443 frontLogin(onSuccess, onError); 444 })); 445 } else { 446 // Użytkownik nie jest zalogowany 447 if ('function' === typeof onError) { 448 onError(EMPIKFOTO.statement().error().id('fbLoginError') 449 .context(that.getName()) 450 .msg(EMPIKFOTO.localize.get('fbLoginError')) 451 .btn(EMPIKFOTO.localize.get('fbFrontLogin'), function () { 452 frontLogin(onSuccess, onError); 453 })); 454 } 455 } 456 }); 457 }; 458 that.isConnected = isConnected; 459 //</editor-fold> 460 461 //<editor-fold defaultstate="collapsed" desc="isAlbumList"> 462 isAlbumList = function () { 463 return true; 464 }; 465 that.isAlbumList = isAlbumList; 466 //</editor-fold> 467 468 //<editor-fold defaultstate="collapsed" desc="hasSearcher"> 469 hasSearcher = function () { 470 return false; 471 }; 472 that.hasSearcher = hasSearcher; 473 //</editor-fold> 474 475 //<editor-fold defaultstate="collapsed" desc="setSearchParams"> 476 setSearchParams = function() 477 { 478 return true; 479 }; 480 that.setSearchParams = setSearchParams; 481 //</editor-fold> 482 483 //<editor-fold defaultstate="collapsed" desc="getMediaCount"> 484 getMediaCount = function () { 485 return 0; 486 }; 487 that.getMediaCount = getMediaCount; 488 //</editor-fold> 489 490 //<editor-fold defaultstate="collapsed" desc="getUserAlbums"> 491 getUserAlbums = function (pageData, onSuccess, onError) { 492 if (initError) { 493 onError(initError); 494 return false; 495 } 496 if (!inited) { 497 afterInit.push(function () { 498 getAlbums(pageData, onSuccess, onError); 499 }); 500 return true; 501 } 502 FB.api( 503 '/me/albums', 504 {fields: ['id', 'name', 'created_time', 'count', 'cover_photo', 'picture']}, 505// {}, 506 function (albumResponse) { 507//window.console.log('#albumResponse', albumResponse); 508 if (!albumResponse || albumResponse.error) { 509 errorDesc(albumResponse); 510 if ('function' === typeof onError) { 511 onError(EMPIKFOTO.statement().error().id('fbAlbumListLoadingProblem') 512 .context(that.getName()) 513 .msg(EMPIKFOTO.localize.get('fbAlbumListLoadingProblem')) 514 .btn(EMPIKFOTO.localize.get('fbTryAgain'), function () { 515 getUserAlbums(pageData, onSuccess, onError); 516 })); 517 } 518 } else { 519 if ('function' === typeof onSuccess) { 520 onSuccess(that.getAlbums(albumResponse)); 521 } 522 } 523 } 524 ); 525 }; 526 that.getUserAlbums = getUserAlbums; 527 //</editor-fold> 528 529 //<editor-fold defaultstate="collapsed" desc="getPhotosForAlbumId"> 530 getPhotosForAlbumId = function (ignore, albumId, onSuccess, onError) { 531// albumId = 90090; 532 if (!inited) { 533 afterInit.push(function () { 534 getPhotosForAlbumId(albumId, onSuccess, onError); 535 }); 536 return true; 537 } 538 FB.api( 539 '/' + albumId + '/photos', 540 {fields: ['id', 'picture', 'source', 'created_time', 'width', 'height']}, 541// {}, 542 function (albumPhotosResponse) { 543//window.console.log('#albumPhotosResponse', albumPhotosResponse); 544 if (!albumPhotosResponse || albumPhotosResponse.error) { 545 errorDesc(albumPhotosResponse); 546 if ('function' === typeof onError) {
547 onError(EMPIKFOTO.statement().error().id('fbPhotosListLoadingProblem') 548 .context(that.getName()) 549 .msg(EMPIKFOTO.localize.get('fbPhotosListLoadingProblem')) 550 // .msg(errorDesc(albumPhotosResponse.error), false) 551 .btn(EMPIKFOTO.localize.get('fbTryAgain'), function () { 552 getPhotosForAlbumId(albumId, onSuccess, onError); 553 })); 554 } 555 } else { 556 if ('function' === typeof onSuccess) { 557 onSuccess(that.getPhotos(albumPhotosResponse)); 558 } 559 } 560 } 561 ); 562 }; 563 that.getPhotosForAlbumId = getPhotosForAlbumId; 564 //</editor-fold> 565 566 //<editor-fold defaultstate="collapsed" desc="getPhotos"> 567 // Na podstawie odpowiedzi o zdjÄcia z albumu tworzy obiekty image w tablicy 568 // Dodaje też link do kolejnej strony do wczytania 569 getPhotos = function (photos, onSuccess, onError) { 570 var result = { 571 data: [], 572 paging: {} 573 }, 574 i, 575 jqxhr; 576 577 if ('object' === typeof photos) { 578 if (photos.hasOwnProperty('data')) { 579 for (i = 0; i < photos.data.length; i += 1) { 580 result.data.push(EMPIKFOTO.image({ 581 type: 'social', 582 typeForStats: 'facebook', 583 small: photos.data[i].picture, 584 large: photos.data[i].source, 585 date: new Date(photos.data[i].created_time), 586 width: photos.data[i].width, 587 height: photos.data[i].height, 588 size: photos.data[i].size, 589 service: that 590 }, my)); 591 } 592 } 593 if (photos.hasOwnProperty('paging')) { 594 if (photos.paging.hasOwnProperty('next')) { 595 result.paging.next = photos.paging.next; 596 } 597 if (photos.paging.hasOwnProperty('prev')) { 598 result.paging.prev = photos.paging.prev; 599 } 600 } 601 } 602 if ('string' === typeof photos) { 603 jqxhr = $.getJSON(photos, function (data, textStatus, jqXHR) { 604 if (onSuccess) { 605 onSuccess(that.getPhotos(data)); 606 } 607 }).fail(function (jqXHR, textStatus, errorThrown) { 608 if (onSuccess) { 609 onSuccess(that.getPhotos([])); 610 } 611 }); 612 } 613 return result; 614 }; 615 that.getPhotos = getPhotos; 616 //</editor-fold> 617 618 //<editor-fold defaultstate="collapsed" desc="getAlbums"> 619 // Na podstawie odpowiedzi o zdjÄcia z albumu tworzy obiekty album w tablicy 620 // Dodaje też link do kolejnej strony do wczytania 621 getAlbums = function (albums, onSuccess, onError) { 622 var result = { 623 data: [], 624 paging: {} 625 }, 626 i, 627 jqxhr; 628 629 if ('object' === typeof albums) { 630 if (albums.hasOwnProperty('data')) { 631 for (i = 0; i < albums.data.length; i += 1) { 632 result.data.push(EMPIKFOTO.album({ 633 id: String(albums.data[i].id), 634 date: new Date(albums.data[i].created_time), 635 name: $('<div />').text(albums.data[i].name).html(), 636 count: albums.data[i].count, 637 thumbnail: albums.data[i].picture.data.url, 638 bigThumbnail: albums.data[i].picture.data.url, 639 midview: albums.data[i].picture.data.url, 640 thumbnailId: albums.data[i].cover_photo 641 }, my)); 642 } 643 } 644 if (albums.hasOwnProperty('paging')) { 645 if (albums.paging.hasOwnProperty('next')) { 646 result.paging.next = albums.paging.next; 647 } 648 if (albums.paging.hasOwnProperty('prev')) { 649 result.paging.prev = albums.paging.prev; 650 } 651 } 652 } 653 if ('string' === typeof albums) { 654 jqxhr = $.getJSON(albums, function (data, textStatus, jqXHR) { 655 if (onSuccess) { 656 onSuccess(that.getAlbums(data)); 657 } 658 }).fail(function (jqXHR, textStatus, errorThrown) { 659 if (onSuccess) { 660 onSuccess(that.getPhotos([])); 661 } 662 }); 663 } 664//console.log('#result', result); 665 return result; 666 }; 667 that.getAlbums = getAlbums; 668 //</editor-fold> 669 670 //<editor-fold defaultstate="collapsed" desc="getName"> 671 getName = function () { 672 return 'fb'; 673 }; 674 that.getName = getName; 675 //</editor-fold> 676 677 //<editor-fold defaultstate="collapsed" desc="setLoginContainer"> 678 setLoginContainer = function (place, loginContainer, showMethod, hideMethod) { 679 return true; 680 }; 681 that.setLoginContainer = setLoginContainer; 682 //</editor-fold> 683 684 //<editor-fold defaultstate="collapsed" desc="errorDesc"> 685 errorDesc = function (resultObject) { 686 var errorObject = {}, 687 result = []; 688 689 if (resultObject && resultObject.error) { 690 errorObject = resultObject.error; 691 } 692 if (errorObject.hasOwnProperty('type')) { 693 result.push(errorObject.type); 694 } 695 if (errorObject.hasOwnProperty('code')) { 696 result.push('(kod bÅÄdu: ' + errorObject.code + ')'); 697 } 698 if (errorObject.hasOwnProperty('message')) { 699 result.push(errorObject.message); 700 } 701 702 result = result.join(' '); 703 704 if (window.console && window.console.error) { 705 window.console.error(result); 706 } else if (window.console && window.console.log) { 707 window.console.log(result); 708 } 709 return result; 710 }; 711 //</editor-fold> 712 713 return that; 714}; 715//</editor-fold> 716
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.