Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Convert insights to server-side rendering |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | main | trunk |
Files: | files | file ages | folders |
SHA3-256: |
b43f96ceab09292ff2e294338acbaaa2 |
User & Date: | scstarkey 2024-07-09 19:53:31 |
Context
2024-07-09
| ||
20:13 | Fixed log message check-in: 96a94a9c64 user: scstarkey tags: main, trunk | |
19:53 | Convert insights to server-side rendering check-in: b43f96ceab user: scstarkey tags: main, trunk | |
2024-07-07
| ||
15:09 | Make blog posts more readable check-in: 6d097f5856 user: scstarkey tags: main, trunk | |
Changes
Changes to src/main/java/app/coffeetime/models/events/Event.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package app.coffeetime.models.events; import app.coffeetime.models.IceboxTopic; import app.coffeetime.models.Linkable; import app.coffeetime.models.ParticipantInfo; import app.coffeetime.models.RoundStatus; import app.coffeetime.models.chat.ChatMessage; import app.coffeetime.util.DateInfo; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.time.DurationFormatUtils; | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | package app.coffeetime.models.events; import app.coffeetime.models.IceboxTopic; import app.coffeetime.models.Linkable; import app.coffeetime.models.ParticipantInfo; import app.coffeetime.models.RoundStatus; import app.coffeetime.models.chat.ChatMessage; import app.coffeetime.util.Crypto; import app.coffeetime.util.DateInfo; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apache.commons.lang3.time.DurationFormatUtils; |
︙ | ︙ | |||
943 944 945 946 947 948 949 950 951 952 953 954 955 956 | return trialBalloonStatus; } @Override public PVector<Link> getLinks() { return links; } @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.JSON_STYLE) .append("now", now) .append("internalId", internalId) .append("id", id) | > > > > > > > | 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 | return trialBalloonStatus; } @Override public PVector<Link> getLinks() { return links; } public String getInsightHash() { return Crypto.md5Hash(this.getInsights().stream() .map(EventInsight::hashCode) .map(String::valueOf) .collect(Collectors.joining())); } @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.JSON_STYLE) .append("now", now) .append("internalId", internalId) .append("id", id) |
︙ | ︙ |
Changes to src/main/java/app/coffeetime/models/events/EventAction.java.
1 2 3 4 5 6 7 8 9 10 11 | package app.coffeetime.models.events; import app.coffeetime.util.Markdown; import com.fasterxml.jackson.annotation.JsonIgnore; public class EventAction { private final int internalId; private final String id; private final int userId; private final String creatorName; private final int topicId; | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | package app.coffeetime.models.events; import app.coffeetime.util.Markdown; import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.Objects; public class EventAction { private final int internalId; private final String id; private final int userId; private final String creatorName; private final int topicId; |
︙ | ︙ | |||
85 86 87 88 89 90 91 | sb.append(", action='").append(action).append('\''); sb.append(", actionPublic=").append(actionPublic); sb.append(", complete=").append(complete); sb.append(", ownedByMe=").append(ownedByMe); sb.append('}'); return sb.toString(); } | | > > > > > > > > > > > > > | 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | sb.append(", action='").append(action).append('\''); sb.append(", actionPublic=").append(actionPublic); sb.append(", complete=").append(complete); sb.append(", ownedByMe=").append(ownedByMe); sb.append('}'); return sb.toString(); } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final EventAction that = (EventAction) o; return actionPublic == that.actionPublic && complete == that.complete && Objects.equals(id, that.id) && Objects.equals(action, that.action); } @Override public int hashCode() { return Objects.hash(id, action, actionPublic, complete); } } |
Changes to src/main/java/app/coffeetime/models/events/EventInsight.java.
1 2 3 4 5 6 7 8 9 10 11 | package app.coffeetime.models.events; import app.coffeetime.util.Markdown; import com.fasterxml.jackson.annotation.JsonIgnore; public class EventInsight { private final int internalId; private final String id; private final int userId; private final String creatorName; private final int topicId; | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 | package app.coffeetime.models.events; import app.coffeetime.util.Markdown; import com.fasterxml.jackson.annotation.JsonIgnore; import java.util.Objects; public class EventInsight { private final int internalId; private final String id; private final int userId; private final String creatorName; private final int topicId; |
︙ | ︙ | |||
82 83 84 85 86 87 88 | sb.append(", topicId=").append(topicId); sb.append(", insight='").append(insight).append('\''); sb.append(", insightPublic=").append(insightPublic); sb.append(", ownedByMe=").append(ownedByMe); sb.append('}'); return sb.toString(); } | | > > > > > > > > > > > > > | 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | sb.append(", topicId=").append(topicId); sb.append(", insight='").append(insight).append('\''); sb.append(", insightPublic=").append(insightPublic); sb.append(", ownedByMe=").append(ownedByMe); sb.append('}'); return sb.toString(); } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final EventInsight that = (EventInsight) o; return insightPublic == that.insightPublic && Objects.equals(id, that.id) && Objects.equals(insight, that.insight); } @Override public int hashCode() { return Objects.hash(id, insight, insightPublic); } } |
Changes to src/main/java/app/coffeetime/models/events/EventTopic.java.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | package app.coffeetime.models.events; import app.coffeetime.util.Markdown; import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; public class EventTopic { public enum TopicStatus {available, inProcess, processed} private final int internalId; private final String id; private final String topic; private final String topicHtml; | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | package app.coffeetime.models.events; import app.coffeetime.util.Markdown; import com.fasterxml.jackson.annotation.JsonIgnore; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import java.util.Objects; public class EventTopic { public enum TopicStatus {available, inProcess, processed} private final int internalId; private final String id; private final String topic; private final String topicHtml; |
︙ | ︙ | |||
103 104 105 106 107 108 109 | .append("creatorName", creatorName) .append("requesterEmail", requesterEmail) .append("requesterVotes", requesterVotes) .append("status", status) .append("allVotes", allVotes) .toString(); } | | > > > > > > > > > > > > > | 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | .append("creatorName", creatorName) .append("requesterEmail", requesterEmail) .append("requesterVotes", requesterVotes) .append("status", status) .append("allVotes", allVotes) .toString(); } @Override public boolean equals(final Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final EventTopic that = (EventTopic) o; return allVotes == that.allVotes && Objects.equals(id, that.id) && Objects.equals(topic, that.topic) && status == that.status; } @Override public int hashCode() { return Objects.hash(id, topic, status, allVotes); } } |
Changes to src/main/java/app/coffeetime/web/EventRouting.java.
︙ | ︙ | |||
376 377 378 379 380 381 382 | return addFlash(super.sessionProperties(), "leave.success"); } }; })); router.POST("/event/:eventId/insight", wrapRequireApiUser(userModel, sessionProperties, (email, req) -> withLoadedEvent(email, true, eventModel, req, (event, form, eventNotFound) -> { | | | < | | | | > > | | | > > > | | < | > | < | 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 | return addFlash(super.sessionProperties(), "leave.success"); } }; })); router.POST("/event/:eventId/insight", wrapRequireApiUser(userModel, sessionProperties, (email, req) -> withLoadedEvent(email, true, eventModel, req, (event, form, eventNotFound) -> { final Optional<String> text = Optional.ofNullable(form.get("insight")); if (event.isRequesterParticipant()) { text.filter(StringUtils::isNotBlank).ifPresent(s -> eventModel.addInsight(event.getId(), email, req.timeZone(), s)); } else { LOG.get().warn(format("User '%s' requested to add insight to event '%s' but they aren't a participant", email, event.getId())); } return eventInsightsFragment(mergeTool, eventModel, req); }))); router.POST("/event/:eventId/insight/:insightId/delete", wrapRequireApiUser(userModel, sessionProperties, (email, request) -> { final String insightId = request.requestPropertyAsString("insightId"); try { eventModel.deleteInsight(email, insightId); return eventInsightsFragment(mergeTool, eventModel, request); } catch (UserFriendlyRuntimeException e) { LOG.get().error("Unable to delete insight", e); return redirect(toEvent(request), e.getUserFriendlyMessage()); } })); router.GET("/event/:eventId/insightsFragment", wrapAuth(userModel, sessionProperties, req -> eventInsightsFragment(mergeTool, eventModel, req))); router.POST("/event/:eventId/insight/:insightId/public", wrapRequireApiUser(userModel, sessionProperties, (email, req) -> withLoadedEvent(email, true, eventModel, req, (event, form, eventNotFound) -> { final boolean shouldBePublic = Optional.ofNullable(form.get("public")) .map(Boolean::parseBoolean).orElseThrow(); final String insightId = req.requestPropertyAsString("insightId"); event.getInsights() .stream() .filter(insight -> insight.getId().equals(insightId)) .findFirst() .ifPresentOrElse(insight -> eventModel.setInsightAccess(email, insightId, shouldBePublic), () -> LOG.get().warn(format("Can't make missing insight '%s' public", insightId))); return eventInsightsFragment(mergeTool, eventModel, req); }))); router.POST("/event/:eventId/nextStep", wrapRequireApiUser(userModel, sessionProperties, (email, req) -> withLoadedEvent(email, true, eventModel, req, (event, form, eventNotFound) -> { final Optional<String> text = Optional.ofNullable(form.get("text")); return event.isRequesterParticipant() ? text.map(s -> StringUtils.isBlank(s) ? null : |
︙ | ︙ | |||
689 690 691 692 693 694 695 696 697 698 699 700 701 702 | if (flagIs("MODE", "dev")) { eventModel.forceToEnd(email, req.timeZone(), eventId); return success(); } return redirect("/", "Access denied"); })); } @NotNull private static Response emailForm(final Request req, final String email, final Event event, final MergeTool mergeTool, final String template, | > > > > > > > > > | 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 | if (flagIs("MODE", "dev")) { eventModel.forceToEnd(email, req.timeZone(), eventId); return success(); } return redirect("/", "Access denied"); })); } private static Response eventInsightsFragment(final MergeTool mergeTool, final EventModel eventModel, final Request req) { return withLoadedEvent(req.principal().orElseThrow(), false, eventModel, req, (event, eventNotFound) -> htmlResponse(renderFile(mergeTool, req, "/templates/web/eventFragments/insightList.vm", TreePMap.singleton("event", event)))); } @NotNull private static Response emailForm(final Request req, final String email, final Event event, final MergeTool mergeTool, final String template, |
︙ | ︙ | |||
1121 1122 1123 1124 1125 1126 1127 | @NotNull private static HttpResponseStatus translateContentResult(final EventModel.EventContentResult result) { return result.status.isSuccess() ? HttpResponseStatus.OK : result.status.isMissing() ? HttpResponseStatus.NOT_FOUND : HttpResponseStatus.BAD_REQUEST; } | < < < < < < < < < | 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 | @NotNull private static HttpResponseStatus translateContentResult(final EventModel.EventContentResult result) { return result.status.isSuccess() ? HttpResponseStatus.OK : result.status.isMissing() ? HttpResponseStatus.NOT_FOUND : HttpResponseStatus.BAD_REQUEST; } private static Response addEventNextStep(final EventModel eventModel, final String eventId, final String email, final TimeZone timeZone, final String nextStep) { final EventModel.EventContentResult result = eventModel.addNextStep(eventId, email, timeZone, nextStep); return plainResponse(translateContentResult(result), result.contentId); |
︙ | ︙ |
Changes to src/main/js/index.ts.
1 2 3 4 5 6 7 | export * from './charting'; export * from './chatBox'; export * from './chatMsg'; export * from './crypto'; export * from './currentSpeaker'; export * from './eventApi'; export * from './helloWorld'; | < < | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | export * from './charting'; export * from './chatBox'; export * from './chatMsg'; export * from './crypto'; export * from './currentSpeaker'; export * from './eventApi'; export * from './helloWorld'; export * from './nextStep'; export * from './nextStepsList'; export * from './participantList'; export * from './testing'; export * from './textUtil'; export * from './topic'; export * from './topicsList'; |
︙ | ︙ |
Deleted src/main/js/insight.ts.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Deleted src/main/js/insightsList.ts.
|
| < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < < |
Changes to src/main/resources/templates/web/event.vm.
︙ | ︙ | |||
304 305 306 307 308 309 310 | <div class="context-section"> <div class="heading-wrapper"> <h2>Insights</h2> </div> <em class="section-hint">We'll send you a list after the event</em> | > > > > | | > > | | > | > | > > | > > > > | | | > | 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 | <div class="context-section"> <div class="heading-wrapper"> <h2>Insights</h2> </div> <em class="section-hint">We'll send you a list after the event</em> <a href="/event/${event.id}/insightsFragment" id="insights-reload" ts-req ts-req-method="get" ts-target="#insights-list" style="display: none">reload</a> #parse("/templates/web/eventFragments/insightList.vm") <form id="add-insight" class="add-item-row" action="/event/${event.id}/insight" method="post" ts-req="" ts-target="#insights-list" ts-req-after="target '#new-insight', attr value ''"> <input id="new-insight" class="add-content-element" disabled="disabled" type="text" name="insight" placeholder="I learned..." autocomplete="off" maxlength="200"/> <input type="hidden" name="${csrfTokenName}" value="${csrfToken}"/> <div class="new-item-btns"> <button type="submit" class="add-content-element" disabled="disabled" id="new-insight-btn">add </button> </div> </form> </div> </div> </div> |
︙ | ︙ | |||
386 387 388 389 390 391 392 393 394 395 396 397 398 399 | let videoSize = 0; //Don't play the gong on first page load let roundCompleted = true; let participantCount = 0; let currentTopic = null; let currentTopicId = null; #if(${event.standardVoting}) let continuationVote = null; let allContinuationVotes = {}; #end let participants = []; | > | 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 | let videoSize = 0; //Don't play the gong on first page load let roundCompleted = true; let participantCount = 0; let currentTopic = null; let currentTopicId = null; let insightHash = "${event.insightHash}"; #if(${event.standardVoting}) let continuationVote = null; let allContinuationVotes = {}; #end let participants = []; |
︙ | ︙ | |||
440 441 442 443 444 445 446 | return serverAddItem("new-next-step", "new-next-step-btn", "/event/${event.id}/nextStep", "text", () => refreshEvent(false), () => { alert("Unable to add next step at this time. Please try again later."); }); } | < < < < < < < < | 456 457 458 459 460 461 462 463 464 465 466 467 468 469 | return serverAddItem("new-next-step", "new-next-step-btn", "/event/${event.id}/nextStep", "text", () => refreshEvent(false), () => { alert("Unable to add next step at this time. Please try again later."); }); } function sendStartTopic(topicId) { const req = new XMLHttpRequest(); req.onreadystatechange = function () { if (req.readyState === XMLHttpRequest.DONE) { if (req.status === 200) { refreshEvent(false); |
︙ | ︙ | |||
567 568 569 570 571 572 573 | if (currentTopic || eventStatus === 'inProgress') { R.forEach((roundTimeE) => { roundTimeE.innerText = roundDurationRemaining; }, roundTimeElements); } #end | > | | > | 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 | if (currentTopic || eventStatus === 'inProgress') { R.forEach((roundTimeE) => { roundTimeE.innerText = roundDurationRemaining; }, roundTimeElements); } #end if (event.insightHash !== insightHash) { document.getElementById("insights-reload").click(); insightHash = event.insightHash; } document.querySelector("next-steps-list").dispatchEvent(new CustomEvent("next-steps-refreshed", {detail: event.actions})); document.querySelector("topics-list").dispatchEvent(new CustomEvent("topics-refreshed", { detail: { |
︙ | ︙ |
Added src/main/resources/templates/web/eventFragments/insightList.vm.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | <ul id="insights-list" class="collapsible context-item-list"> <script>insightHash = "${event.insightHash}";</script> #foreach($insight in $event.insights) #if(${insight.insightPublic} || ${insight.ownedByMe}) <li id="insight-${insight.id}"> <div class="context-item-text"> #if(${insight.ownedByMe}) <form id="updateAccessForm" method="post" action="/event/${event.id}/insight/${insight.id}/public" ts-req="" ts-target="#insights-list"> <button name="public" type="submit" class="action material-icons context-action" type="button">#if(${insight.insightPublic}) #else#end</button> #set($newValue = ! ${insight.insightPublic}) <input type="hidden" name="public" value="${newValue}"/> <input type="hidden" name="${csrfTokenName}" value="${csrfToken}"/> </form> #else <span class="context-access"></span> #end <div class="context-item-info"> <div class="context-item-description"> ${insight.insightHtml} </div> #if(${insight.ownedByMe}) <span></span> #else <span class="context-item-creator">${insight.creatorName}</span> #end </div> #if(${insight.ownedByMe}) <form id="updateAccessForm" method="post" action="/event/${event.id}/insight/${insight.id}/delete" ts-req="" ts-target="#insights-list" ts-req-before="confirm 'Delete this insight?'"> <button name="public" type="submit" class="action material-icons context-action" type="button"></button> <input type="hidden" name="${csrfTokenName}" value="${csrfToken}"/> </form> #end </div> </li> #end #end </ul> |