From 920d861bbfccecbb744596dd92c1a0ad70677161 Mon Sep 17 00:00:00 2001 From: Chad Brower Date: Tue, 2 May 2023 15:40:54 -0700 Subject: [PATCH 1/2] Highlight reason words that match new reason in progress --- @client/edit_point.coffee | 5 +++++ @client/point.coffee | 3 ++- @client/pro_con_widget.coffee | 16 ++++++++++++- @client/shared.coffee | 42 ++++++++++++++++++++++++++++++++++- 4 files changed, 63 insertions(+), 3 deletions(-) diff --git a/@client/edit_point.coffee b/@client/edit_point.coffee index 7d30703dd..a4018dbae 100644 --- a/@client/edit_point.coffee +++ b/@client/edit_point.coffee @@ -56,6 +56,9 @@ window.EditPoint = ReactiveComponent position: 'absolute' right: 0 top: -21 + onInput: (event) => + if event and event.target + setStateNewPointInput(extractWords(event.target.value)) INPUT id:'is_pro' @@ -242,6 +245,8 @@ window.EditPoint = ReactiveComponent your_points.editing_points = _.without your_points.editing_points, @props.point save your_points + clearStateNewPointInput() + savePoint : (ev) -> diff --git a/@client/point.coffee b/@client/point.coffee index 4f69e7ea7..e74e7935b 100644 --- a/@client/point.coffee +++ b/@client/point.coffee @@ -20,6 +20,7 @@ window.Point = ReactiveComponent current_user = fetch('/current_user') + new_point_words = getStateNewPointInput() renderIncluders = (draw_all_includers) => @@ -178,7 +179,7 @@ window.Point = ReactiveComponent DIV className: 'point_nutshell' - splitParagraphs point.nutshell, append + splitParagraphs point.nutshell, append, new_point_words diff --git a/@client/pro_con_widget.coffee b/@client/pro_con_widget.coffee index 1713055d7..d4519ec89 100644 --- a/@client/pro_con_widget.coffee +++ b/@client/pro_con_widget.coffee @@ -20,6 +20,18 @@ window.getProposalMode = (proposal) -> local_state.mode +window.clearStateNewPointInput = -> setStateNewPointInput(null) + +window.setStateNewPointInput = ( new_point_input ) -> + state_of_new_point_input = fetch('new_point_input') + state_of_new_point_input['words'] = new_point_input + save state_of_new_point_input + +window.getStateNewPointInput = ( new_point_input ) -> + state_of_new_point_input = fetch('new_point_input') + return if state_of_new_point_input then state_of_new_point_input['words'] else null + + window.update_proposal_mode = (proposal, proposal_mode, triggered_by) -> can_opine = canUserOpine proposal proposal = fetch proposal @@ -31,6 +43,8 @@ window.update_proposal_mode = (proposal, proposal_mode, triggered_by) -> (can_opine == Permission.DISABLED && your_opinion.key)) proposal_mode = 'results' + else + clearStateNewPointInput() local_state = fetch shared_local_key proposal if local_state.mode != proposal_mode @@ -1501,4 +1515,4 @@ GroupSelectionRegion = ReactiveComponent color: focus_color() left: "min(#{wrapper_width - name_width}px, max(0px, calc(#{left}px - #{name_width / 2}px)))" #Math.min(wrapper_width - name_width - 10, Math.max(0, left - name_width / 2)) title - \ No newline at end of file + diff --git a/@client/shared.coffee b/@client/shared.coffee index 12e60b47e..716f34958 100644 --- a/@client/shared.coffee +++ b/@client/shared.coffee @@ -506,9 +506,40 @@ safe_string = (user_content) -> user_content -window.splitParagraphs = (user_content, append) -> + +window.STOP_WORDS = new Set( [ + "a", "about", "above", "after", "again", "against", "all", "am", "an", "and", "any", "are", "aren't", + "as", "at", "be", "because", "been", "before", "being", "below", "between", "both", "but", "by", + "can't", "cannot", "could", "couldn't", "did", "didn't", "do", "does", "doesn't", "doing", "don't", + "down", "during", "each", "few", "for", "from", "further", "had", "hadn't", "has", "hasn't", "have", + "haven't", "having", "he", "he'd", "he'll", "he's", "her", "here", "here's", "hers", "herself", "him", + "himself", "his", "how", "how's", "i", "i'd", "i'll", "i'm", "i've", "if", "in", "into", "is", "isn't", + "it", "it's", "its", "itself", "let's", "me", "more", "most", "mustn't", "my", "myself", "nor", "not", + "of", "off", "on", "once", "only", "or", "other", "ought", "our", "ours", "ourselves", "out", "over", + "own", "same", "shan't", "she", "she'd", "she'll", "she's", "should", "shouldn't", "so", "some", "such", + "than", "that", "that's", "the", "their", "theirs", "them", "themselves", "then", "there", "there's", + "these", "they", "they'd", "they'll", "they're", "they've", "this", "those", "through", "to", "too", + "under", "until", "up", "very", "was", "wasn't", "we", "we'd", "we'll", "we're", "we've", "were", + "weren't", "what", "what's", "when", "when's", "where", "where's", "which", "while", "who", "who's", + "whom", "why", "why's", "with", "won't", "would", "wouldn't", "you", "you'd", "you'll", "you're", + "you've", "your", "yours", "yourself", "yourselves" ] ) + +window.extractWords = ( text ) -> + # Split discarding non-alpha-numeric delimiters, lowercase, filter stop-words + words = if text then text.toLowerCase().split( /\W+/ ) else [] + return words.filter( (w) -> !STOP_WORDS.has(w) and (1 < w.length) ) + +window.extractTokens = ( text ) -> + # Split keeping delimiters, punctuation, original case + tokens = if text then text.split( /(\W+)/ ) else [] + return tokens + + +window.splitParagraphs = (user_content, append, highlightWords) -> if !user_content return SPAN null + + highlightWords = highlightWords or null user_content = safe_string user_content @@ -527,6 +558,15 @@ window.splitParagraphs = (user_content, append) -> if text.substring(0,5) == 'link:' A key: idx, href: text.substring(5, text.length), target: '_blank', text.substring(5, text.length) + else if highlightWords + SPAN + key: idx + for token,tokenIndex in extractTokens(text) + SPAN + key:tokenIndex + style: + backgroundColor: if highlightWords.includes(token.toLowerCase()) then 'yellow' else null + token else SPAN key: idx, text From e11abd197dd516010b096c0619d7d9445d0da7db Mon Sep 17 00:00:00 2001 From: Chad Brower Date: Sat, 6 May 2023 19:03:19 -0700 Subject: [PATCH 2/2] While editing a new point, show only existing points that match words in new point --- @client/point.coffee | 5 ++++ @client/pro_con_widget.coffee | 50 ++++++++++++++++++++++++----------- @client/shared.coffee | 3 +++ 3 files changed, 42 insertions(+), 16 deletions(-) diff --git a/@client/point.coffee b/@client/point.coffee index e74e7935b..404df2cff 100644 --- a/@client/point.coffee +++ b/@client/point.coffee @@ -112,6 +112,9 @@ window.Point = ReactiveComponent else append = null + do_display = !@props.any_point_matches_new_input or !new_point_words or (new_point_words.length <= 0) or \ + containsAnyWord(point.nutshell, new_point_words) + LI key: "point-#{point.id}" 'data-id': @props.point @@ -122,6 +125,8 @@ window.Point = ReactiveComponent if (is_selected && e.which == 27) || e.which == 13 || e.which == 32 @selectPoint(e) e.preventDefault() + style: + display: if do_display then null else 'none' if @props.rendered_as == 'decision_board_point' DIV diff --git a/@client/pro_con_widget.coffee b/@client/pro_con_widget.coffee index d4519ec89..7052b18af 100644 --- a/@client/pro_con_widget.coffee +++ b/@client/pro_con_widget.coffee @@ -340,6 +340,28 @@ window.Reasons = ReactiveComponent local_proposal.has_focus = has_focus save local_proposal + # Collect point lists, and check whether any existing point matches the new point being written. + points_community_cons = buildPointsList \ + proposal, 'cons', \ + (if mode == 'results' then 'score' else 'last_inclusion'), \ + mode == 'crafting' && !TABLET_SIZE(), \ + mode == 'crafting' || TABLET_SIZE() || (just_you && mode == 'results') + points_community_pros = buildPointsList \ + proposal, 'pros', \ + (if mode == 'results' then 'score' else 'last_inclusion'), \ + mode == 'crafting' && !TABLET_SIZE(), \ + mode == 'crafting' || TABLET_SIZE() || (just_you && mode == 'results') + points_my_pros = (p for p in your_opinion.point_inclusions or [] \ + when fetch(p).is_pro) + points_my_cons = (p for p in your_opinion.point_inclusions or [] \ + when !fetch(p).is_pro) + new_point_words = getStateNewPointInput() + any_point_matches_new_input = false + if new_point_words + all_points = [].concat( points_community_cons, points_community_pros, points_my_pros, points_my_cons ) + all_points_data = (fetch(point) for point in all_points) + any_point_matches_new_input = Boolean( all_points_data.some( (point) => containsAnyWord(point.nutshell, new_point_words) ) ) + DIV className: "slow-thought #{if mode == 'crafting' then 'crafting' else 'summary'}" style: @@ -407,6 +429,9 @@ window.Reasons = ReactiveComponent DecisionBoard key: 'decisionboard' proposal: proposal.key + points_my_cons: points_my_cons + points_my_pros: points_my_pros + any_point_matches_new_input: any_point_matches_new_input DIV style: @@ -421,15 +446,11 @@ window.Reasons = ReactiveComponent valence: 'cons' points_draggable: mode == 'crafting' drop_target: false - points: buildPointsList \ - proposal, 'cons', \ - (if mode == 'results' then 'score' else 'last_inclusion'), \ - mode == 'crafting' && !TABLET_SIZE(), \ - mode == 'crafting' || TABLET_SIZE() || (just_you && mode == 'results') + points: points_community_cons style: visibility: if !TABLET_SIZE() && mode == 'crafting' && !has_community_points then 'hidden' in_viewport: @local.in_viewport - + any_point_matches_new_input: any_point_matches_new_input #community pros PointsList @@ -441,14 +462,11 @@ window.Reasons = ReactiveComponent valence: 'pros' points_draggable: mode == 'crafting' drop_target: false - points: buildPointsList \ - proposal, 'pros', \ - (if mode == 'results' then 'score' else 'last_inclusion'), \ - mode == 'crafting' && !TABLET_SIZE(), \ - mode == 'crafting' || TABLET_SIZE() || (just_you && mode == 'results') + points: points_community_pros style: visibility: if !TABLET_SIZE() && mode == 'crafting' && !has_community_points then 'hidden' in_viewport: @local.in_viewport + any_point_matches_new_input: any_point_matches_new_input if !show_all_points BUTTON @@ -695,8 +713,8 @@ window.DecisionBoard = ReactiveComponent points_editable: true points_draggable: true drop_target: are_points_in_wings - points: (p for p in your_opinion.point_inclusions or [] \ - when fetch(p).is_pro) + points: @props.points_my_pros + any_point_matches_new_input: @props.any_point_matches_new_input PointsList key: 'your_con_points' @@ -707,9 +725,8 @@ window.DecisionBoard = ReactiveComponent points_editable: true points_draggable: true drop_target: are_points_in_wings - points: (p for p in your_opinion.point_inclusions or [] \ - when !fetch(p).is_pro) - + points: @props.points_my_cons + any_point_matches_new_input: @props.any_point_matches_new_input DIV style: {clear: 'both'} @@ -1014,6 +1031,7 @@ window.PointsList = ReactiveComponent your_points_key: @props.reasons_key enable_dragging: @props.points_draggable in_viewport: @props.in_viewport + any_point_matches_new_input: @props.any_point_matches_new_input else if points.length == 0 && @props.rendered_as == 'community_point' && mode == "results" opinion_views = fetch 'opinion_views' diff --git a/@client/shared.coffee b/@client/shared.coffee index 716f34958..e72870f84 100644 --- a/@client/shared.coffee +++ b/@client/shared.coffee @@ -529,6 +529,9 @@ window.extractWords = ( text ) -> words = if text then text.toLowerCase().split( /\W+/ ) else [] return words.filter( (w) -> !STOP_WORDS.has(w) and (1 < w.length) ) + window.containsAnyWord = ( text, words ) -> + return words and extractTokens(text).some( (token) => words.find((word) => (word==token.toLowerCase())) ) + window.extractTokens = ( text ) -> # Split keeping delimiters, punctuation, original case tokens = if text then text.split( /(\W+)/ ) else []