1# frozen_string_literal: true 2 3require 'task_list' 4require 'task_list/filter' 5 6# Contains functionality for objects that can have task lists in their 7# descriptions. Task list items can be added with Markdown like "* [x] Fix 8# bugs". 9# 10# Used by MergeRequest and Issue 11module Taskable 12 COMPLETED = 'completed' 13 INCOMPLETE = 'incomplete' 14 COMPLETE_PATTERN = /(\[[xX]\])/.freeze 15 INCOMPLETE_PATTERN = /(\[\s\])/.freeze 16 ITEM_PATTERN = %r{ 17 ^ 18 (?:(?:>\s{0,4})*) # optional blockquote characters 19 (?:\s*(?:[-+*]|(?:\d+\.)))+ # list prefix (one or more) required - task item has to be always in a list 20 \s+ # whitespace prefix has to be always presented for a list item 21 (\[\s\]|\[[xX]\]) # checkbox 22 (\s.+) # followed by whitespace and some text. 23 }x.freeze 24 25 def self.get_tasks(content) 26 content.to_s.scan(ITEM_PATTERN).map do |checkbox, label| 27 # ITEM_PATTERN strips out the hyphen, but Item requires it. Rabble rabble. 28 TaskList::Item.new("- #{checkbox}", label.strip) 29 end 30 end 31 32 def self.get_updated_tasks(old_content:, new_content:) 33 old_tasks = get_tasks(old_content) 34 new_tasks = get_tasks(new_content) 35 36 new_tasks.select.with_index do |new_task, i| 37 old_task = old_tasks[i] 38 next unless old_task 39 40 new_task.source == old_task.source && new_task.complete? != old_task.complete? 41 end 42 end 43 44 # Called by `TaskList::Summary` 45 def task_list_items 46 return [] if description.blank? 47 48 @task_list_items ||= Taskable.get_tasks(description) # rubocop:disable Gitlab/ModuleWithInstanceVariables 49 end 50 51 def tasks 52 @tasks ||= TaskList.new(self) 53 end 54 55 # Return true if this object's description has any task list items. 56 def tasks? 57 tasks.summary.items? 58 end 59 60 # Return a string that describes the current state of this Taskable's task 61 # list items, e.g. "12 of 20 tasks completed" 62 def task_status(short: false) 63 return '' if description.blank? 64 65 prep, completed = if short 66 ['/', ''] 67 else 68 [' of ', ' completed'] 69 end 70 71 sum = tasks.summary 72 "#{sum.complete_count}#{prep}#{sum.item_count} #{'task'.pluralize(sum.item_count)}#{completed}" 73 end 74 75 # Return a short string that describes the current state of this Taskable's 76 # task list items -- for small screens 77 def task_status_short 78 task_status(short: true) 79 end 80 81 def task_completion_status 82 @task_completion_status ||= { 83 count: tasks.summary.item_count, 84 completed_count: tasks.summary.complete_count 85 } 86 end 87end 88