| | @@ -18,18 +18,18 @@ |
| 18 | 18 | loadToolbar: undefined /* the load-posts toolbar (dynamically created) */, |
| 19 | 19 | inputWrapper: E1("#chat-input-area"), |
| 20 | 20 | messagesWrapper: E1('#chat-messages-wrapper') |
| 21 | 21 | }, |
| 22 | 22 | me: F.user.name, |
| 23 | | - mxMsg: F.config.chatInitSize ? -F.config.chatInitSize : -50, |
| 23 | + mxMsg: F.config.chat.initSize ? -F.config.chat.initSize : -50, |
| 24 | 24 | mnMsg: undefined/*lowest message ID we've seen so far (for history loading)*/, |
| 25 | 25 | pageIsActive: 'visible'===document.visibilityState, |
| 26 | 26 | changesSincePageHidden: 0, |
| 27 | 27 | notificationBubbleColor: 'white', |
| 28 | 28 | totalMessageCount: 0, // total # of inbound messages |
| 29 | 29 | //! Number of messages to load for the history buttons |
| 30 | | - loadMessageCount: Math.abs(F.config.chatInitSize || 20), |
| 30 | + loadMessageCount: Math.abs(F.config.chat.initSize || 20), |
| 31 | 31 | /* Alignment of 'my' messages: must be 'left' or 'right'. Note |
| 32 | 32 | that 'right' is conventional for mobile chat apps but can be |
| 33 | 33 | difficult to read in wide windows (desktop/tablet landscape |
| 34 | 34 | mode). Can be toggled via settings popup. */ |
| 35 | 35 | msgMyAlign: (window.innerWidth<window.innerHeight) ? 'right' : 'left', |
| | @@ -85,12 +85,24 @@ |
| 85 | 85 | mip.parentNode.insertBefore(e, mip.nextSibling); |
| 86 | 86 | }else{ |
| 87 | 87 | mip.parentNode.appendChild(e); |
| 88 | 88 | } |
| 89 | 89 | } |
| 90 | + }, |
| 91 | + settings:{ |
| 92 | + get: (k,dflt)=>F.storage.get(k,dflt), |
| 93 | + getBool: (k,dflt)=>F.storage.getBool(k,dflt), |
| 94 | + set: (k,v)=>F.storage.set(k,v), |
| 95 | + defaults:{ |
| 96 | + "images-inline": !!F.config.chat.imagesInline |
| 97 | + } |
| 90 | 98 | } |
| 91 | 99 | }; |
| 100 | + Object.keys(cs.settings.defaults).forEach(function f(k){ |
| 101 | + const v = cs.settings.get(k,f); |
| 102 | + if(f===v) cs.settings.set(k,cs.settings.defaults[k]); |
| 103 | + }); |
| 92 | 104 | cs.pageTitleOrig = cs.e.pageTitle.innerText; |
| 93 | 105 | const qs = (e)=>document.querySelector(e); |
| 94 | 106 | const argsToArray = function(args){ |
| 95 | 107 | return Array.prototype.slice.call(args,0); |
| 96 | 108 | }; |
| | @@ -368,22 +380,23 @@ |
| 368 | 380 | adjustY: function(y){ |
| 369 | 381 | const rect = settingsButton.getBoundingClientRect(); |
| 370 | 382 | return rect.top + rect.height + 2; |
| 371 | 383 | } |
| 372 | 384 | }); |
| 373 | | - settingsPopup.installClickToHide(); |
| 374 | | - |
| 375 | 385 | /* Settings menu entries... */ |
| 376 | 386 | const settingsOps = [{ |
| 377 | | - label: "Toggle page body", |
| 387 | + label: "Toggle chat-only mode", |
| 388 | + tooltip: "Toggles the page's header and footer on and off.", |
| 378 | 389 | callback: function f(){ |
| 379 | 390 | if(undefined === f.isHidden){ |
| 380 | 391 | f.isHidden = false; |
| 381 | 392 | f.elemsToToggle = []; |
| 382 | 393 | document.body.childNodes.forEach(function(e){ |
| 383 | 394 | if(!e.classList) return/*TEXT nodes and such*/; |
| 384 | | - else if(!e.classList.contains('content')){ |
| 395 | + else if(!e.classList.contains('content') |
| 396 | + && !e.classList.contains('fossil-PopupWidget') |
| 397 | + /*kludge^^^ for settingsPopup click handling!*/){ |
| 385 | 398 | f.elemsToToggle.push(e); |
| 386 | 399 | } |
| 387 | 400 | }); |
| 388 | 401 | /* In order to make the input area opaque, such that the |
| 389 | 402 | message list scrolls under it without being visible, we |
| | @@ -399,23 +412,25 @@ |
| 399 | 412 | */ |
| 400 | 413 | f.initialBg = Chat.e.messagesWrapper.style.backgroundColor; |
| 401 | 414 | const cs = window.getComputedStyle(document.body); |
| 402 | 415 | f.inheritedBg = cs.backgroundColor; |
| 403 | 416 | } |
| 404 | | - const cs = Chat.e.inputWrapper.style; |
| 417 | + const iws = Chat.e.inputWrapper.style; |
| 405 | 418 | if((f.isHidden = !f.isHidden)){ |
| 406 | 419 | D.addClass(f.elemsToToggle, 'hidden'); |
| 407 | 420 | D.addClass(document.body, 'chat-only-mode'); |
| 408 | | - cs.backgroundColor = f.inheritedBg; |
| 421 | + iws.backgroundColor = f.inheritedBg; |
| 409 | 422 | }else{ |
| 410 | 423 | D.removeClass(f.elemsToToggle, 'hidden'); |
| 411 | 424 | D.removeClass(document.body, 'chat-only-mode'); |
| 412 | | - cs.backgroundColor = f.initialBg; |
| 425 | + iws.backgroundColor = f.initialBg; |
| 413 | 426 | } |
| 414 | 427 | } |
| 415 | 428 | },{ |
| 416 | 429 | label: "Toggle left/right layout", |
| 430 | + tooltip: "Toggles your own messages between the right (mobile-style) "+ |
| 431 | + "or left of the screen (more readable on large windows).", |
| 417 | 432 | callback: function f(){ |
| 418 | 433 | if('right'===Chat.msgMyAlign) Chat.msgMyAlign = 'left'; |
| 419 | 434 | else Chat.msgMyAlign = 'right'; |
| 420 | 435 | const msgs = Chat.e.messagesWrapper.querySelectorAll('.message-row'); |
| 421 | 436 | msgs.forEach(function(row){ |
| | @@ -423,23 +438,49 @@ |
| 423 | 438 | row.querySelector('legend').setAttribute('align', Chat.msgMyAlign); |
| 424 | 439 | if('right'===Chat.msgMyAlign) row.style.justifyContent = "flex-end"; |
| 425 | 440 | else row.style.justifyContent = "flex-start"; |
| 426 | 441 | }); |
| 427 | 442 | } |
| 443 | + },{ |
| 444 | + label: "Toggle images inline", |
| 445 | + persistent: true, |
| 446 | + tooltip: "Toggles whether newly-arrived images appear "+ |
| 447 | + "inline or as download links.", |
| 448 | + callback: function(){ |
| 449 | + const v = Chat.settings.getBool('images-inline',true); |
| 450 | + Chat.settings.set('images-inline', !v); |
| 451 | + F.toast.message("Image mode set to "+(v ? "hyperlink" : "inline")+"."); |
| 452 | + } |
| 428 | 453 | }]; |
| 429 | 454 | |
| 430 | 455 | settingsOps.forEach(function(op){ |
| 431 | | - const btn = D.append(D.span(), op.label); |
| 432 | | - D.append(settingsPopup.e, btn); |
| 456 | + const line = D.addClass(D.span(), 'menu-entry'); |
| 457 | + const btn = D.append(D.addClass(D.span(), 'button'), |
| 458 | + (op.persistent ? "[P] " : "")+op.label); |
| 433 | 459 | op.callback.button = btn; |
| 434 | 460 | if('function'===op.init) op.init(); |
| 461 | + if(op.tooltip){ |
| 462 | + const help = D.span(); |
| 463 | + D.append(line, help); |
| 464 | + F.helpButtonlets.create(help, op.tooltip); |
| 465 | + } |
| 466 | + D.append(line, btn); |
| 467 | + D.append(settingsPopup.e, line); |
| 435 | 468 | btn.addEventListener('click', function(ev){ |
| 436 | 469 | settingsPopup.hide(); |
| 437 | 470 | op.callback.call(this,ev); |
| 438 | 471 | }); |
| 439 | 472 | }); |
| 440 | | - settingsButton.addEventListener('click',()=>settingsPopup.show(settingsButton), false); |
| 473 | + D.append(settingsPopup.e, D.append(D.span(),"[P] = locally-persistent setting")); |
| 474 | + // settingsPopup.installClickToHide();// Don't do this for this popup! |
| 475 | + settingsButton.addEventListener('click',function(ev){ |
| 476 | + //ev.preventDefault(); |
| 477 | + if(settingsPopup.isShown()) settingsPopup.hide(); |
| 478 | + else settingsPopup.show(settingsButton); |
| 479 | + /* Reminder: we cannot toggle the visibility from her |
| 480 | + */ |
| 481 | + }, false); |
| 441 | 482 | |
| 442 | 483 | /* Find an ideal X position for the popup, directly under the settings |
| 443 | 484 | button, based on the size of the popup... */ |
| 444 | 485 | settingsPopup.show(document.body); |
| 445 | 486 | popupSize = settingsPopup.e.getBoundingClientRect(); |
| | @@ -501,19 +542,24 @@ |
| 501 | 542 | } |
| 502 | 543 | let eContent = D.addClass(D.div(),'message-content','chat-message'); |
| 503 | 544 | eContent.style.backgroundColor = m.uclr; |
| 504 | 545 | row.appendChild(eContent); |
| 505 | 546 | if( m.fsize>0 ){ |
| 506 | | - if( m.fmime && m.fmime.startsWith("image/") ){ |
| 547 | + if( m.fmime |
| 548 | + && m.fmime.startsWith("image/") |
| 549 | + && Chat.settings.getBool('images-inline',true) |
| 550 | + ){ |
| 507 | 551 | eContent.appendChild(D.img("chat-download/" + m.msgid)); |
| 508 | 552 | }else{ |
| 509 | | - eContent.appendChild(D.a( |
| 553 | + const a = D.a( |
| 510 | 554 | window.fossil.rootPath+ |
| 511 | 555 | 'chat-download/' + m.msgid+'/'+encodeURIComponent(m.fname), |
| 512 | 556 | // ^^^ add m.fname to URL to cause downloaded file to have that name. |
| 513 | 557 | "(" + m.fname + " " + m.fsize + " bytes)" |
| 514 | | - )); |
| 558 | + ) |
| 559 | + D.attr(a,'target','_blank'); |
| 560 | + eContent.appendChild(a); |
| 515 | 561 | } |
| 516 | 562 | const br = D.br(); |
| 517 | 563 | br.style.clear = "both"; |
| 518 | 564 | eContent.appendChild(br); |
| 519 | 565 | } |
| | @@ -540,12 +586,12 @@ |
| 540 | 586 | }else{ |
| 541 | 587 | Chat.changesSincePageHidden += jx.msgs.length; |
| 542 | 588 | Chat.e.pageTitle.innerText = '('+Chat.changesSincePageHidden+') '+ |
| 543 | 589 | Chat.pageTitleOrig; |
| 544 | 590 | } |
| 545 | | - if(jx.msgs.length && F.config.pingTcp){ |
| 546 | | - fetch("http:/"+"/localhost:"+window.fossil.config.pingTcp+"/chat-ping"); |
| 591 | + if(jx.msgs.length && F.config.chat.pingTcp){ |
| 592 | + fetch("http:/"+"/localhost:"+F.config.chat.pingTcp+"/chat-ping"); |
| 547 | 593 | } |
| 548 | 594 | }/*newcontent()*/; |
| 549 | 595 | |
| 550 | 596 | (function(){ |
| 551 | 597 | /** Add toolbar for loading older messages. We use a FIELDSET here |
| 552 | 598 | |