Class: Rave::Models::Blip

Inherits:
Component show all
Includes:
Rave::Mixins::Logger, Rave::Mixins::TimeUtils
Defined in:
lib/models/blip.rb,
lib/ops/blip_ops.rb

Overview

Represents a blip, containing formated text, gadgets and other elements. It is part of a Wavelet within a Wave.

Constant Summary collapse

JAVA_CLASS =

:nodoc:

'com.google.wave.api.impl.BlipData'
VALID_STATES =

:nodoc: As passed to initializer in :state option.

[:normal, :null, :deleted]
VALID_FORMATS =

Reopen the blip class and add operation-related methods

[:plain, :html, :textile]

Constants inherited from Component

Component::GENERATED_PATTERN, Component::GENERATED_PREFIX

Instance Attribute Summary

Attributes inherited from Component

#context

Instance Method Summary collapse

Methods included from Rave::Mixins::Logger

#logger

Methods included from Rave::Mixins::TimeUtils

#time_from_json

Methods inherited from Component

#generated?, #id, #unique_id

Constructor Details

#initialize(options = {}) ⇒ Blip

Options include:

  • :annotations

  • :child_blip_ids

  • :content

  • :contributors

  • :creator

  • :elements

  • :last_modified_time

  • :parent_blip_id

  • :version

  • :wave_id

  • :wavelet_id

  • :id

  • :context

  • :state



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/models/blip.rb', line 136

def initialize(options = {}) # :nodoc:
  @annotations = options[:annotations] || []
  @child_blip_ids = options[:child_blip_ids] || []
  @content = options[:content] || ''
  @contributor_ids = options[:contributors] || []
  @creator = options[:creator] || User::NOBODY_ID
  @elements = options[:elements] || {}
  @last_modified_time = time_from_json(options[:last_modified_time]) || Time.now
  @parent_blip_id = options[:parent_blip_id]
  @version = options[:version] || -1
  @wave_id = options[:wave_id]
  @wavelet_id = options[:wavelet_id]
  @state = options[:state] || :normal

  unless VALID_STATES.include? @state
    raise ArgumentError.new("Bad state #{options[:state]}. Should be one of #{VALID_STATES.join(', ')}")
  end

  # If the blip doesn't have a defined ID, since we just created it,
  # assign a temporary, though unique, ID, based on the ID of the wavelet.
  if options[:id].nil?
    options[:id] = "#{GENERATED_PREFIX}_blip_#{unique_id}"
  end

  super(options)
end

Instance Method Details

#add_annotation(annotation) ⇒ Object

Adds an annotation to the Blip.



169
170
171
172
# File 'lib/models/blip.rb', line 169

def add_annotation(annotation)
  @annotations << annotation
  self
end

#add_child_blip(blip) ⇒ Object

Adds a created child blip to this blip.



184
185
186
187
# File 'lib/models/blip.rb', line 184

def add_child_blip(blip) # :nodoc:
  @child_blip_ids << blip.id
  @context.add_blip(blip)
end

#annotate_document(name, value) ⇒ Object

Annotates the entire content.

NOT IMPLEMENTED



108
109
110
# File 'lib/ops/blip_ops.rb', line 108

def annotate_document(name, value)
  raise NotImplementedError
end

#annotationsObject

Annotations on the blip [Array of Annotation]



17
18
19
# File 'lib/models/blip.rb', line 17

def annotations # :nodoc:
  @annotations.dup
end

#append_element(element) ⇒ Object

Appends an element



203
204
205
206
207
208
209
# File 'lib/ops/blip_ops.rb', line 203

def append_element(element)
  # TODO: What happens if there already is an element at end of content?
  @elements[@content.length] = element
  add_operation(:type => Operation::DOCUMENT_ELEMENT_APPEND, :property => element)

  element
end

#append_inline_blipObject

Appends an inline blip to this blip. Returns: Blip created by operation [Blip]



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/ops/blip_ops.rb', line 128

def append_inline_blip
  # TODO: What happens if there already is an element at end of content?
  blip = Blip.new(:wave_id => @wave_id, :wavelet_id => @wavelet_id)
  @context.add_blip(blip)
  element = Element::InlineBlip.new('blipId' => blip.id)
  element.context = @context
  @elements[@content.length] = element
  add_operation(:type => Operation::DOCUMENT_INLINE_BLIP_APPEND, :property => blip)
  
  blip
end

#append_text(text, options = {}) ⇒ Object

Appends text to the end of the blip’s current content.

Options

:format - Format of the text, which can be any one of:

  • :html - Text marked up with HTML.

  • :plain - Plain text (default).

  • :textile - Text marked up with textile.

Returns: The new content string [String]

Raises:



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/ops/blip_ops.rb', line 67

def append_text(text, options = {})
  format = options[:format] || :plain
  raise BadOptionError.new(:format, VALID_FORMATS, format) unless VALID_FORMATS.include? format
  
  plain_text = text
  
  if format == :textile
    text = RedCloth.new(text).to_html
    format = :html # Can now just treat it as HTML.
  end

  if format == :html
    type = Operation::DOCUMENT_APPEND_MARKUP
    plain_text = strip_html_tags(text)
  else
    type = Operation::DOCUMENT_APPEND
  end
  
  add_operation(:type => type, :property => text)
  # TODO: Add annotations for the tags we removed?
  @content += plain_text # Plain text added to text field.

  @content.dup
end

#child_blip_idsObject

IDs of the children of this blip [Array of String]



22
23
24
# File 'lib/models/blip.rb', line 22

def child_blip_ids # :nodoc:
  @child_blip_ids.map { |id| id.dup }
end

#child_blipsObject

List of direct children of this blip. The first one will be continuing the thread, others will be indented replies [Array of Blip]



109
110
111
# File 'lib/models/blip.rb', line 109

def child_blips # :nodoc:
  @child_blip_ids.map { |id| @context.blips[id] }
end

#clearObject

Clear the content.



9
10
11
12
# File 'lib/ops/blip_ops.rb', line 9

def clear
  return if content.empty? # No point telling the server to clear an empty blip.
  delete_range(0..(@content.length))
end

#contentObject

Text contained in the blip [String]



93
94
95
# File 'lib/models/blip.rb', line 93

def content # :nodoc:
  @content.dup
end

#context=(value) ⇒ Object

Ensure that all elements within the blip are given a context.



114
115
116
117
# File 'lib/models/blip.rb', line 114

def context=(value) # :nodoc:
  super(value)
  @elements.each_value { |e| e.context = value }
end

#contributor_idsObject

IDs (email addresses) of those who have altered this blip [Array of String]



27
28
29
# File 'lib/models/blip.rb', line 27

def contributor_ids # :nodoc:
  @contributor_ids.map { |id| id.dup }
end

#contributorsObject

Users that have made a contribution to the blip [Array of User]



98
99
100
# File 'lib/models/blip.rb', line 98

def contributors # :nodoc:
  @contributor_ids.map { |c| @context.users[c] }
end

#create_child_blipObject

Creates a child blip under this blip



175
176
177
178
179
180
181
# File 'lib/models/blip.rb', line 175

def create_child_blip
  blip = Blip.new(:wave_id => @wave_id, :parent_blip_id => @id, :wavelet_id => @wavelet_id,
    :context => @context, :contributors => [Robot.instance.id])
  @context.add_operation(:type => Operation::BLIP_CREATE_CHILD, :blip_id => @id, :wave_id => @wave_id, :wavelet_id => @wavelet_id, :property => blip)
  add_child_blip(blip)
  blip
end

#creatorObject

Original creator of the blip [User]



103
104
105
# File 'lib/models/blip.rb', line 103

def creator # :nodoc:
  @context.users[@creator]
end

#deleteObject

Delete this blip from its wavelet. Returns the blip id.



200
201
202
203
204
205
206
207
208
209
210
# File 'lib/models/blip.rb', line 200

def delete
  if deleted?
    logger.warning("Attempt to delete blip that has already been deleted: #{id}")
  elsif root?
    logger.warning("Attempt to delete root blip: #{id}")
  else
    @context.add_operation(:type => Operation::BLIP_DELETE,
      :blip_id => @id, :wave_id => @wave_id, :wavelet_id => @wavelet_id)
    delete_me
  end
end

#delete_annotation_by_name(name) ⇒ Object

Deletes the annotation with the given name.

NOT IMPLEMENTED



115
116
117
# File 'lib/ops/blip_ops.rb', line 115

def delete_annotation_by_name(name)
  raise NotImplementedError
end

#delete_annotation_in_range(range, name) ⇒ Object

Deletes the annotations with the given key in the given range.

NOT IMPLEMENTED



122
123
124
# File 'lib/ops/blip_ops.rb', line 122

def delete_annotation_in_range(range, name)
  raise NotImplementedError
end

#delete_element(position) ⇒ Object

Deletes an element at the given position.



169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/ops/blip_ops.rb', line 169

def delete_element(position)
  element = @elements[position]
  case element
  when Element::InlineBlip
    return delete_inline_blip(element.blip)
  when Element
    @elements[position] = nil
    add_operation(:type => Operation::DOCUMENT_ELEMENT_DELETE, :index => position)
  else
    raise "No element to delete at position #{position}"
  end

  self
end

#delete_inline_blip(blip) ⇒ Object

Deletes an inline blip from this blip.

value

Inline blip to delete [Blip]

Returns: Blip ID of the deleted blip [String]



144
145
146
147
148
149
150
151
152
# File 'lib/ops/blip_ops.rb', line 144

def delete_inline_blip(blip) # :nodoc:
  element = @elements.values.find { |e| e.kind_of?(Element::InlineBlip) and e.blip == blip }
  raise "Blip '#{blip.id}' is not an inline blip of blip '#{id}'" if element.nil?
  #element.blip.destroy_me # TODO: How to deal with children?
  @elements.delete_if { |pos, el| el == element }
  add_operation(:type => Operation::DOCUMENT_INLINE_BLIP_DELETE, :property => blip.id)

  blip.id
end

#delete_me(allow_destroy = true) ⇒ Object

INTERNAL Delete the blip or, if appropriate, destroy it instead.



273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/models/blip.rb', line 273

def delete_me(allow_destroy = true) # :nodoc:
  raise "Can't delete root blip" if root?

  if leaf? and allow_destroy
    destroy_me
  else
    # Blip is marked as deleted, but stays in place to maintain structure.
    @state = :deleted
    @content = ''
  end

  @id
end

#delete_range(range) ⇒ Object

Deletes text in the given range. Returns: An empty string [String]

Raises:

  • (ArgumentError)


94
95
96
97
98
99
100
101
102
103
# File 'lib/ops/blip_ops.rb', line 94

def delete_range(range)
  raise ArgumentError.new("Requires a Range, not a #{range.class.name}") unless range.kind_of? Range
  
  add_operation(:type => Operation::DOCUMENT_DELETE, :index => range.min, :property => range)

   @content[range] = ''
   # TODO: Shift and/or delete annotations.

  ''
end

#deleted?Boolean

Has the blip been deleted? [Boolean]

Returns:

  • (Boolean)


83
84
85
# File 'lib/models/blip.rb', line 83

def deleted? # :nodoc:
  [:deleted, :null].include? @state
end

#elementsObject

Elements contained within this blip [Array of Element]



32
33
34
# File 'lib/models/blip.rb', line 32

def elements # :nodoc:
  @elements.dup
end

#has_annotation?(name) ⇒ Boolean

Returns true if an annotation with the given name exists in this blip

Returns:

  • (Boolean)


164
165
166
# File 'lib/models/blip.rb', line 164

def has_annotation?(name)
  @annotations.any? { |a| a.name == name }
end

#insert_element(position, element) ⇒ Object

Inserts the given element in the given position.



185
186
187
188
189
190
191
# File 'lib/ops/blip_ops.rb', line 185

def insert_element(position, element)
  # TODO: Complain if element does exist at that position.
  @elements[position] = element
  add_operation(:type => Operation::DOCUMENT_ELEMENT_INSERT, :index => position, :property => element)

  element
end

#insert_inline_blip(position) ⇒ Object

Inserts an inline blip at the given position. Returns: Blip element created by operation [Blip]



156
157
158
159
160
161
162
163
164
165
166
# File 'lib/ops/blip_ops.rb', line 156

def insert_inline_blip(position)
  # TODO: Complain if element does exist at that position.
  blip = Blip.new(:wave_id => @wave_id, :wavelet_id => @wavelet_id)
  @context.add_blip(blip)
  element = Element::InlineBlip.new('blipId' => blip.id)
  element.context = @context
  @elements[@content.length] = element
  add_operation(:type => Operation::DOCUMENT_INLINE_BLIP_INSERT, :index => position, :property => blip)

  blip
end

#insert_text(index, text) ⇒ Object

Insert text at an index.



15
16
17
18
19
20
21
# File 'lib/ops/blip_ops.rb', line 15

def insert_text(index, text)
  add_operation(:type => Operation::DOCUMENT_INSERT, :index => index, :property => text)
  @content.insert(index, text)
  # TODO: Shift annotations.

  text
end

#last_modified_timeObject

Last time the blip was altered [Time]



37
38
39
# File 'lib/models/blip.rb', line 37

def last_modified_time # :nodoc:
  @last_modified_time.dup
end

#leaf?Boolean

Returns true if this is a leaf node (has no children). [Boolean]

Returns:

  • (Boolean)


78
79
80
# File 'lib/models/blip.rb', line 78

def leaf? # :nodoc:
  @child_blip_ids.empty?
end

#null?Boolean

Has the blip been completely destroyed? [Boolean]

Returns:

  • (Boolean)


88
89
90
# File 'lib/models/blip.rb', line 88

def null? # :nodoc:
  @state == :null
end

#parent_blipObject

Blip that this Blip is a direct reply to. Will be nil if the root blip in a wavelet [Blip or nil for a root blip]



68
69
70
# File 'lib/models/blip.rb', line 68

def parent_blip # :nodoc:
  @context.blips[@parent_blip_id]
end

#parent_blip_idObject

ID of this blip’s parent [String or nil for a root blip]



42
43
44
# File 'lib/models/blip.rb', line 42

def parent_blip_id # :nodoc:
  @parent_blip_id.nil? ? nil : @parent_blip_id.dup
end

INTERNAL Write out a formatted block of text showing the blip and its descendants.



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# File 'lib/models/blip.rb', line 231

def print_structure(indent = 0) # :nodoc:
  str = "#{'  ' * indent}#{to_s}\n"

  unless @child_blip_ids.empty?
    # Move the first blip to the end, since it will be looked at last.
    blip_ids = @child_blip_ids
    blip_ids.push(blip_ids.shift)

    # All children, except the first, should be indented.
    blip_ids.each_with_index do |blip_id, index|
      is_last_blip = (index == blip_ids.size - 1)
      
      # All except the last one should be indented again.
      ind = is_last_blip ? indent : indent + 1
      blip = @context.blips[blip_id]
      if blip
        str << blip.print_structure(ind)
      else
        str << "#{'  ' * ind}<undefined-blip>:#{blip_id}\n"
      end

      str << "\n" unless is_last_blip # Gap between reply chains.
    end
  end

  str
end

#remove_child_blip(blip) ⇒ Object

INTERNAL Removed a child blip.



191
192
193
194
195
196
# File 'lib/models/blip.rb', line 191

def remove_child_blip(blip) # :nodoc:
  @child_blip_ids.delete(blip.id)

  # Destroy oneself completely if you are no longer useful to structure.
  destroy_me if deleted? and leaf? and not root?
end

#replace_element(position, element) ⇒ Object

Replaces the element at the given position with the given element.



194
195
196
197
198
199
200
# File 'lib/ops/blip_ops.rb', line 194

def replace_element(position, element)
  # TODO: Complain if element does not exist at that position.
  @elements[position] = element
  add_operation(:type => Operation::DOCUMENT_ELEMENT_REPLACE, :index => position, :property => element)

  element
end

#root?Boolean

Returns true if this is a root blip (no parent blip) [Boolean]

Returns:

  • (Boolean)


73
74
75
# File 'lib/models/blip.rb', line 73

def root? # :nodoc:
  @parent_blip_id.nil?
end

#set_text(text, options = {}) ⇒ Object

Set the content text of the blip.

Options

:format - Format of the text, which can be any one of:

  • :html - Text marked up with HTML.

  • :plain - Plain text (default).

  • :textile - Text marked up with textile.

Returns: An empty string [String]



32
33
34
35
# File 'lib/ops/blip_ops.rb', line 32

def set_text(text, options = {})
  clear
  append_text(text, options)
end

#set_text_in_range(range, text) ⇒ Object

Deletes the text in a given range and replaces it with the given text. Returns: The text altered [String]

Raises:

  • (ArgumentError)


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/ops/blip_ops.rb', line 39

def set_text_in_range(range, text)
  raise ArgumentError.new("Requires a Range, not a #{range.class.name}") unless range.kind_of? Range
  
  #Note: I'm doing this in the opposite order from the python API, because
  # otherwise, if you are setting text at the end of the content, the cursor
  # gets moved to the start of the range...
  unless text.empty?
    begin # Failures in this method should give us a range error.
      insert_text(range.min, text)
    rescue IndexError => e
      raise RangeError.new(e.message)
    end
  end
  delete_range(range.min+text.length..range.max+text.length)
  # TODO: Shift annotations.

  text
end

#to_jsonObject

INTERNAL Convert to json for sending in an operation. We should never need to send more data than this, although blips we receive will have more data.



262
263
264
265
266
267
268
269
# File 'lib/models/blip.rb', line 262

def to_json # :nodoc:
  {
    'blipId' => @id,
    'javaClass' => JAVA_CLASS,
    'waveId' => @wave_id,
    'waveletId' => @wavelet_id
  }.to_json
end

#to_sObject

Convert to string.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/models/blip.rb', line 213

def to_s
  str = @content.gsub(/\n/, "\\n")
  str = str.length > 24 ? "#{str[0..20]}..." : str
  
  str = case @state
  when :normal
    "#{contributors.join(',')}:#{str}"
  when :deleted
    '<DELETED>'
  when :null
    '<NULL>'
  end

  "#{super}:#{str}"
end

#versionObject

Version number of the contents of the blip [Integer]



12
13
14
# File 'lib/models/blip.rb', line 12

def version
  @version.dup
end

#waveObject

Wave that this blip is a part of [Wave]



62
63
64
# File 'lib/models/blip.rb', line 62

def wave # :nodoc:
  @context.waves[@wave_id]
end

#wave_idObject

ID of the wave this blip belongs to [String]



47
48
49
# File 'lib/models/blip.rb', line 47

def wave_id # :nodoc:
  @wave_id.nil? ? nil : @wave_id.dup
end

#waveletObject

Wavelet that the blip is a part of [Wavelet]



57
58
59
# File 'lib/models/blip.rb', line 57

def wavelet # :nodoc:
  @context.wavelets[@wavelet_id]
end

#wavelet_idObject

ID of the wavelet this blip belongs to [String]



52
53
54
# File 'lib/models/blip.rb', line 52

def wavelet_id # :nodoc:
  @wavelet_id.nil? ? nil : @wavelet_id.dup
end