rave 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/rave +27 -23
- data/lib/commands/appcfg.rb +9 -0
- data/lib/commands/create.rb +153 -147
- data/lib/commands/server.rb +7 -7
- data/lib/commands/task.rb +156 -0
- data/lib/commands/usage.rb +18 -12
- data/lib/commands/war.rb +27 -50
- data/lib/exceptions.rb +19 -5
- data/lib/ext/logger.rb +7 -0
- data/lib/gems.yaml +9 -0
- data/lib/jars/{appengine-api-1.0-sdk-1.2.1.jar → appengine-api-1.0-sdk-1.3.0.jar} +0 -0
- data/lib/mixins/controller.rb +72 -40
- data/lib/mixins/data_format.rb +206 -168
- data/lib/mixins/logger.rb +19 -0
- data/lib/mixins/object_factory.rb +87 -0
- data/lib/mixins/time_utils.rb +19 -0
- data/lib/models/annotation.rb +148 -18
- data/lib/models/blip.rb +305 -61
- data/lib/models/component.rb +42 -0
- data/lib/models/context.rb +174 -45
- data/lib/models/document.rb +8 -8
- data/lib/models/element.rb +113 -0
- data/lib/models/event.rb +230 -48
- data/lib/models/operation.rb +79 -89
- data/lib/models/range.rb +14 -0
- data/lib/models/robot.rb +78 -60
- data/lib/models/user.rb +62 -0
- data/lib/models/wave.rb +45 -19
- data/lib/models/wavelet.rb +269 -69
- data/lib/ops/blip_ops.rb +233 -134
- data/lib/rave.rb +27 -22
- metadata +96 -77
- data/lib/jars/jruby-core.jar +0 -0
- data/lib/jars/ruby-stdlib.jar +0 -0
data/lib/models/wavelet.rb
CHANGED
@@ -1,69 +1,269 @@
|
|
1
|
-
|
2
|
-
module
|
3
|
-
|
4
|
-
class Wavelet
|
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
|
-
|
61
|
-
|
62
|
-
#
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
1
|
+
module Rave
|
2
|
+
module Models
|
3
|
+
# Represents a Wavelet, owned by a Wave
|
4
|
+
class Wavelet < Component
|
5
|
+
include Rave::Mixins::TimeUtils
|
6
|
+
include Rave::Mixins::Logger
|
7
|
+
|
8
|
+
# Creator of the wavelet if it was generated via an operation.
|
9
|
+
GENERATED_CREATOR = "rusty@a.gwave.com" # :nodoc:
|
10
|
+
|
11
|
+
# Current version number of the wavelet [Integer]
|
12
|
+
attr_reader :version
|
13
|
+
|
14
|
+
# ID of the creator [String]
|
15
|
+
def creator_id # :nodoc:
|
16
|
+
@creator_id.dup
|
17
|
+
end
|
18
|
+
|
19
|
+
# Time the wavelet was created [Time]
|
20
|
+
def creation_time # :nodoc:
|
21
|
+
@creation_time.dup
|
22
|
+
end
|
23
|
+
|
24
|
+
# Documents contained within the wavelet [Array of Document]
|
25
|
+
def data_documents # :nodoc:
|
26
|
+
@data_documents.dup
|
27
|
+
end
|
28
|
+
|
29
|
+
# The last time the wavelet was modified [Time]
|
30
|
+
def last_modified_time # :nodoc:
|
31
|
+
@last_modified_time.dup
|
32
|
+
end
|
33
|
+
|
34
|
+
# ID for the root blip [String]
|
35
|
+
def root_blip_id # :nodoc:
|
36
|
+
@root_blip_id.dup
|
37
|
+
end
|
38
|
+
|
39
|
+
# Wavelet title [String]
|
40
|
+
def title # :nodoc:
|
41
|
+
@title.dup
|
42
|
+
end
|
43
|
+
|
44
|
+
# ID of the wave that the wavelet is a part of [String]
|
45
|
+
def wave_id # :nodoc:
|
46
|
+
@wave_id.dup
|
47
|
+
end
|
48
|
+
|
49
|
+
# IDs of all those who are currently members of the wavelet [Array of String]
|
50
|
+
def participant_ids # :nodoc:
|
51
|
+
@participant_ids.map { |id| id.dup }
|
52
|
+
end
|
53
|
+
|
54
|
+
JAVA_CLASS = 'com.google.wave.api.impl.WaveletData'
|
55
|
+
ROOT_ID_SUFFIX = "conv+root" #The suffix for the root wavelet in a wave]
|
56
|
+
ROOT_ID_REGEXP = /#{Regexp.escape(ROOT_ID_SUFFIX)}$/
|
57
|
+
|
58
|
+
#
|
59
|
+
# Options include:
|
60
|
+
# - :creator
|
61
|
+
# - :creation_time
|
62
|
+
# - :data_documents
|
63
|
+
# - :last_modifed_time
|
64
|
+
# - :participants
|
65
|
+
# - :root_blip_id
|
66
|
+
# - :title
|
67
|
+
# - :version
|
68
|
+
# - :wave_id
|
69
|
+
# - :context
|
70
|
+
# - :id
|
71
|
+
def initialize(options = {}) # :nodoc:
|
72
|
+
@participant_ids = options[:participants] || []
|
73
|
+
|
74
|
+
if options[:id].nil? and options[:context]
|
75
|
+
# Generate the wavelet from scratch.
|
76
|
+
super(:id => "#{GENERATED_PREFIX}_wavelet_#{unique_id}_#{ROOT_ID_SUFFIX}", :context => options[:context])
|
77
|
+
|
78
|
+
# Create a wave to live in.
|
79
|
+
wave = Wave.new(:wavelet_ids => [@id], :context => @context)
|
80
|
+
@wave_id = wave.id
|
81
|
+
@context.add_wave(wave)
|
82
|
+
|
83
|
+
# Ensure the newly created wavelet has a root blip.
|
84
|
+
blip = Blip.new(:wave_id => wave.id, :wavelet_id => @id,
|
85
|
+
:creator => @context.robot.id, :contributors => [@context.robot.id])
|
86
|
+
@context.add_blip(blip)
|
87
|
+
@root_blip_id = blip.id
|
88
|
+
|
89
|
+
@participant_ids.each do |id|
|
90
|
+
@context.add_user(:id => id) unless @context.users[id]
|
91
|
+
end
|
92
|
+
|
93
|
+
@creator_id = GENERATED_CREATOR
|
94
|
+
@context.add_user(:id => @creator_id) unless @context.users[@creator_id]
|
95
|
+
else
|
96
|
+
super(options)
|
97
|
+
@root_blip_id = options[:root_blip_id]
|
98
|
+
@creator_id = options[:creator] || User::NOBODY_ID
|
99
|
+
@wave_id = options[:wave_id]
|
100
|
+
end
|
101
|
+
|
102
|
+
@creation_time = time_from_json(options[:creation_time]) || Time.now
|
103
|
+
@data_documents = options[:data_documents] || {}
|
104
|
+
@last_modified_time = time_from_json(options[:last_modified_time]) || Time.now
|
105
|
+
@title = options[:title] || ''
|
106
|
+
@version = options[:version] || 0
|
107
|
+
end
|
108
|
+
|
109
|
+
# Users that are currently have access the wavelet [Array of User]
|
110
|
+
def participants # :nodoc:
|
111
|
+
@participant_ids.map { |p| @context.users[p] }
|
112
|
+
end
|
113
|
+
|
114
|
+
# Users that originally created the wavelet [User]
|
115
|
+
def creator # :nodoc:
|
116
|
+
@context.users[@creator_id]
|
117
|
+
end
|
118
|
+
|
119
|
+
# Is this the root wavelet for its wave? [Boolean]
|
120
|
+
def root? # :nodoc:
|
121
|
+
not (id =~ ROOT_ID_REGEXP).nil?
|
122
|
+
end
|
123
|
+
|
124
|
+
#Creates a blip for this wavelet
|
125
|
+
# Returns: Gererated blip [Blip]
|
126
|
+
def create_blip
|
127
|
+
parent = final_blip
|
128
|
+
blip = Blip.new(:wave_id => @wave_id, :parent_blip_id => parent.id,
|
129
|
+
:wavelet_id => @id, :context => @context)
|
130
|
+
parent.add_child_blip(blip)
|
131
|
+
|
132
|
+
@context.add_operation(:type => Operation::WAVELET_APPEND_BLIP, :wave_id => @wave_id, :wavelet_id => @id, :property => blip)
|
133
|
+
blip
|
134
|
+
end
|
135
|
+
|
136
|
+
# Find the last blip in the main thread [Blip]
|
137
|
+
def final_blip # :nodoc:
|
138
|
+
blip = @context.blips[@root_blip_id]
|
139
|
+
if blip
|
140
|
+
while blip
|
141
|
+
# Find the first blip that is defined, if at all.
|
142
|
+
child_blip = blip.child_blips.find { |b| not b.nil? }
|
143
|
+
break unless child_blip
|
144
|
+
blip = child_blip
|
145
|
+
end
|
146
|
+
end
|
147
|
+
blip
|
148
|
+
end
|
149
|
+
|
150
|
+
# Adds a participant (human or robot) to the wavelet
|
151
|
+
# +user+:: User to add, as ID or object [String or User]
|
152
|
+
# Returns: The user that was added [User or nil]
|
153
|
+
def add_participant(user) # :nodoc:
|
154
|
+
id = user.to_s.downcase
|
155
|
+
if @participant_ids.include?(id)
|
156
|
+
logger.warning("Attempted to add a participant who was already in the wavelet(#{@id}): #{id}")
|
157
|
+
return nil
|
158
|
+
end
|
159
|
+
|
160
|
+
# Allow string names to be used as participant.
|
161
|
+
user = if @context.users[id]
|
162
|
+
@context.users[id]
|
163
|
+
else
|
164
|
+
@context.add_user(:id => id)
|
165
|
+
end
|
166
|
+
|
167
|
+
@context.add_operation(:type => Operation::WAVELET_ADD_PARTICIPANT,
|
168
|
+
:wave_id => @wave_id, :wavelet_id => @id, :property => user)
|
169
|
+
@participant_ids << id
|
170
|
+
|
171
|
+
user
|
172
|
+
end
|
173
|
+
|
174
|
+
# Removes a participant (robot only) from the wavelet.
|
175
|
+
# +user+:: User to remove, as ID or object [String or User]
|
176
|
+
# Returns: The user that was removed [User or nil]
|
177
|
+
def remove_participant(user) # :nodoc:
|
178
|
+
id = user.to_s.downcase
|
179
|
+
unless @participant_ids.include?(id)
|
180
|
+
logger.warning("Attempted to remove a participant who was not in the wavelet(#{@id}): #{id}")
|
181
|
+
return nil
|
182
|
+
end
|
183
|
+
|
184
|
+
# Allow string names to be used as participant.
|
185
|
+
user = @context.users[id]
|
186
|
+
|
187
|
+
unless user.robot?
|
188
|
+
logger.warning("Attempted to remove a non-robot from wavelet(#{@id}): #{id}")
|
189
|
+
return nil
|
190
|
+
end
|
191
|
+
|
192
|
+
if user == @context.robot
|
193
|
+
return remove_robot
|
194
|
+
end
|
195
|
+
|
196
|
+
@context.add_operation(:type => Operation::WAVELET_REMOVE_PARTICIPANT,
|
197
|
+
:wave_id => @wave_id, :wavelet_id => @id, :property => user)
|
198
|
+
@participant_ids.delete id
|
199
|
+
|
200
|
+
user
|
201
|
+
end
|
202
|
+
|
203
|
+
# Removes the local robot from the wavelet.
|
204
|
+
# Returns: The local robot [Robot]
|
205
|
+
def remove_robot
|
206
|
+
robot = @context.robot
|
207
|
+
@context.add_operation(:type => Operation::WAVELET_REMOVE_SELF,
|
208
|
+
:wave_id => @wave_id, :wavelet_id => @id)
|
209
|
+
@participant_ids.delete robot.id
|
210
|
+
|
211
|
+
robot
|
212
|
+
end
|
213
|
+
|
214
|
+
#Sets the data document for the wavelet
|
215
|
+
#
|
216
|
+
# NOT IMPLEMENTED
|
217
|
+
def set_data_document(name, data)
|
218
|
+
raise NotImplementedError
|
219
|
+
end
|
220
|
+
|
221
|
+
#Set the title
|
222
|
+
#
|
223
|
+
def title=(title) # :nodoc: Documented by title() as accessor.
|
224
|
+
title = title.to_s
|
225
|
+
@context.add_operation(:type => Operation::WAVELET_SET_TITLE,
|
226
|
+
:wave_id => @wave_id, :wavelet_id => @id, :property => title)
|
227
|
+
@title = title
|
228
|
+
end
|
229
|
+
|
230
|
+
# First blip in the wavelet [Blip]
|
231
|
+
def root_blip # :nodoc:
|
232
|
+
@context.blips[@root_blip_id]
|
233
|
+
end
|
234
|
+
|
235
|
+
# Wave that the wavelet is contained within.
|
236
|
+
def wave# :nodoc:
|
237
|
+
@context.waves[@wave_id]
|
238
|
+
end
|
239
|
+
|
240
|
+
# *INTERNAL*
|
241
|
+
# Convert to json for sending in an operation.
|
242
|
+
def to_json # :nodoc:
|
243
|
+
{
|
244
|
+
'waveletId' => @id,
|
245
|
+
'javaClass' => JAVA_CLASS,
|
246
|
+
'waveId' => @wave_id,
|
247
|
+
'rootBlipId' => @root_blip_id,
|
248
|
+
'participants' => { "javaClass" => "java.util.ArrayList", "list" => @participant_ids }
|
249
|
+
}.to_json
|
250
|
+
end
|
251
|
+
|
252
|
+
# Convert to string.
|
253
|
+
def to_s
|
254
|
+
text = @title.length > 24 ? "#{@title[0..20]}..." : @title
|
255
|
+
"#{super}:#{participants.join(',')}:#{text}"
|
256
|
+
end
|
257
|
+
|
258
|
+
def print_structure(indent = 0) # :nodoc:
|
259
|
+
str = "#{' ' * indent}#{to_s}\n"
|
260
|
+
|
261
|
+
if root_blip
|
262
|
+
str << root_blip.print_structure(indent + 1)
|
263
|
+
end
|
264
|
+
|
265
|
+
str
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
data/lib/ops/blip_ops.rb
CHANGED
@@ -1,134 +1,233 @@
|
|
1
|
-
|
2
|
-
module
|
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
|
-
def set_text_in_range(range, text)
|
40
|
-
|
41
|
-
|
42
|
-
#
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
#
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
#TODO
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
#
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
#
|
121
|
-
|
122
|
-
|
123
|
-
raise
|
124
|
-
end
|
125
|
-
|
126
|
-
#Appends an
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
1
|
+
module Rave
|
2
|
+
module Models
|
3
|
+
class Blip
|
4
|
+
# Reopen the blip class and add operation-related methods
|
5
|
+
|
6
|
+
VALID_FORMATS = [:plain, :html, :textile] # :nodoc: For set_text/append_text
|
7
|
+
|
8
|
+
# Clear the content.
|
9
|
+
def clear
|
10
|
+
return if content.empty? # No point telling the server to clear an empty blip.
|
11
|
+
delete_range(0..(@content.length))
|
12
|
+
end
|
13
|
+
|
14
|
+
# Insert text at an index.
|
15
|
+
def insert_text(index, text)
|
16
|
+
add_operation(:type => Operation::DOCUMENT_INSERT, :index => index, :property => text)
|
17
|
+
@content.insert(index, text)
|
18
|
+
# TODO: Shift annotations.
|
19
|
+
|
20
|
+
text
|
21
|
+
end
|
22
|
+
|
23
|
+
# Set the content text of the blip.
|
24
|
+
#
|
25
|
+
# === Options
|
26
|
+
# :+format+ - Format of the text, which can be any one of:
|
27
|
+
# * :+html+ - Text marked up with HTML.
|
28
|
+
# * :+plain+ - Plain text (default).
|
29
|
+
# * :+textile+ - Text marked up with textile.
|
30
|
+
#
|
31
|
+
# Returns: An empty string [String]
|
32
|
+
def set_text(text, options = {})
|
33
|
+
clear
|
34
|
+
append_text(text, options)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Deletes the text in a given range and replaces it with the given text.
|
38
|
+
# Returns: The text altered [String]
|
39
|
+
def set_text_in_range(range, text)
|
40
|
+
raise ArgumentError.new("Requires a Range, not a #{range.class.name}") unless range.kind_of? Range
|
41
|
+
|
42
|
+
#Note: I'm doing this in the opposite order from the python API, because
|
43
|
+
# otherwise, if you are setting text at the end of the content, the cursor
|
44
|
+
# gets moved to the start of the range...
|
45
|
+
unless text.empty?
|
46
|
+
begin # Failures in this method should give us a range error.
|
47
|
+
insert_text(range.min, text)
|
48
|
+
rescue IndexError => e
|
49
|
+
raise RangeError.new(e.message)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
delete_range(range.min+text.length..range.max+text.length)
|
53
|
+
# TODO: Shift annotations.
|
54
|
+
|
55
|
+
text
|
56
|
+
end
|
57
|
+
|
58
|
+
# Appends text to the end of the blip's current content.
|
59
|
+
#
|
60
|
+
# === Options
|
61
|
+
# :+format+ - Format of the text, which can be any one of:
|
62
|
+
# * :+html+ - Text marked up with HTML.
|
63
|
+
# * :+plain+ - Plain text (default).
|
64
|
+
# * :+textile+ - Text marked up with textile.
|
65
|
+
#
|
66
|
+
# Returns: The new content string [String]
|
67
|
+
def append_text(text, options = {})
|
68
|
+
format = options[:format] || :plain
|
69
|
+
raise BadOptionError.new(:format, VALID_FORMATS, format) unless VALID_FORMATS.include? format
|
70
|
+
|
71
|
+
plain_text = text
|
72
|
+
|
73
|
+
if format == :textile
|
74
|
+
text = RedCloth.new(text).to_html
|
75
|
+
format = :html # Can now just treat it as HTML.
|
76
|
+
end
|
77
|
+
|
78
|
+
if format == :html
|
79
|
+
type = Operation::DOCUMENT_APPEND_MARKUP
|
80
|
+
plain_text = strip_html_tags(text)
|
81
|
+
else
|
82
|
+
type = Operation::DOCUMENT_APPEND
|
83
|
+
end
|
84
|
+
|
85
|
+
add_operation(:type => type, :property => text)
|
86
|
+
# TODO: Add annotations for the tags we removed?
|
87
|
+
@content += plain_text # Plain text added to text field.
|
88
|
+
|
89
|
+
@content.dup
|
90
|
+
end
|
91
|
+
|
92
|
+
# Deletes text in the given range.
|
93
|
+
# Returns: An empty string [String]
|
94
|
+
def delete_range(range)
|
95
|
+
raise ArgumentError.new("Requires a Range, not a #{range.class.name}") unless range.kind_of? Range
|
96
|
+
|
97
|
+
add_operation(:type => Operation::DOCUMENT_DELETE, :index => range.min, :property => range)
|
98
|
+
|
99
|
+
@content[range] = ''
|
100
|
+
# TODO: Shift and/or delete annotations.
|
101
|
+
|
102
|
+
''
|
103
|
+
end
|
104
|
+
|
105
|
+
# Annotates the entire content.
|
106
|
+
#
|
107
|
+
# NOT IMPLEMENTED
|
108
|
+
def annotate_document(name, value)
|
109
|
+
raise NotImplementedError
|
110
|
+
end
|
111
|
+
|
112
|
+
# Deletes the annotation with the given name.
|
113
|
+
#
|
114
|
+
# NOT IMPLEMENTED
|
115
|
+
def delete_annotation_by_name(name)
|
116
|
+
raise NotImplementedError
|
117
|
+
end
|
118
|
+
|
119
|
+
# Deletes the annotations with the given key in the given range.
|
120
|
+
#
|
121
|
+
# NOT IMPLEMENTED
|
122
|
+
def delete_annotation_in_range(range, name)
|
123
|
+
raise NotImplementedError
|
124
|
+
end
|
125
|
+
|
126
|
+
# Appends an inline blip to this blip.
|
127
|
+
# Returns: Blip created by operation [Blip]
|
128
|
+
def append_inline_blip
|
129
|
+
# TODO: What happens if there already is an element at end of content?
|
130
|
+
blip = Blip.new(:wave_id => @wave_id, :wavelet_id => @wavelet_id)
|
131
|
+
@context.add_blip(blip)
|
132
|
+
element = Element::InlineBlip.new('blipId' => blip.id)
|
133
|
+
element.context = @context
|
134
|
+
@elements[@content.length] = element
|
135
|
+
add_operation(:type => Operation::DOCUMENT_INLINE_BLIP_APPEND, :property => blip)
|
136
|
+
|
137
|
+
blip
|
138
|
+
end
|
139
|
+
|
140
|
+
# Deletes an inline blip from this blip.
|
141
|
+
# +value+:: Inline blip to delete [Blip]
|
142
|
+
#
|
143
|
+
# Returns: Blip ID of the deleted blip [String]
|
144
|
+
def delete_inline_blip(blip) # :nodoc:
|
145
|
+
element = @elements.values.find { |e| e.kind_of?(Element::InlineBlip) and e.blip == blip }
|
146
|
+
raise "Blip '#{blip.id}' is not an inline blip of blip '#{id}'" if element.nil?
|
147
|
+
#element.blip.destroy_me # TODO: How to deal with children?
|
148
|
+
@elements.delete_if { |pos, el| el == element }
|
149
|
+
add_operation(:type => Operation::DOCUMENT_INLINE_BLIP_DELETE, :property => blip.id)
|
150
|
+
|
151
|
+
blip.id
|
152
|
+
end
|
153
|
+
|
154
|
+
# Inserts an inline blip at the given position.
|
155
|
+
# Returns: Blip element created by operation [Blip]
|
156
|
+
def insert_inline_blip(position)
|
157
|
+
# TODO: Complain if element does exist at that position.
|
158
|
+
blip = Blip.new(:wave_id => @wave_id, :wavelet_id => @wavelet_id)
|
159
|
+
@context.add_blip(blip)
|
160
|
+
element = Element::InlineBlip.new('blipId' => blip.id)
|
161
|
+
element.context = @context
|
162
|
+
@elements[@content.length] = element
|
163
|
+
add_operation(:type => Operation::DOCUMENT_INLINE_BLIP_INSERT, :index => position, :property => blip)
|
164
|
+
|
165
|
+
blip
|
166
|
+
end
|
167
|
+
|
168
|
+
# Deletes an element at the given position.
|
169
|
+
def delete_element(position)
|
170
|
+
element = @elements[position]
|
171
|
+
case element
|
172
|
+
when Element::InlineBlip
|
173
|
+
return delete_inline_blip(element.blip)
|
174
|
+
when Element
|
175
|
+
@elements[position] = nil
|
176
|
+
add_operation(:type => Operation::DOCUMENT_ELEMENT_DELETE, :index => position)
|
177
|
+
else
|
178
|
+
raise "No element to delete at position #{position}"
|
179
|
+
end
|
180
|
+
|
181
|
+
self
|
182
|
+
end
|
183
|
+
|
184
|
+
# Inserts the given element in the given position.
|
185
|
+
def insert_element(position, element)
|
186
|
+
# TODO: Complain if element does exist at that position.
|
187
|
+
@elements[position] = element
|
188
|
+
add_operation(:type => Operation::DOCUMENT_ELEMENT_INSERT, :index => position, :property => element)
|
189
|
+
|
190
|
+
element
|
191
|
+
end
|
192
|
+
|
193
|
+
# Replaces the element at the given position with the given element.
|
194
|
+
def replace_element(position, element)
|
195
|
+
# TODO: Complain if element does not exist at that position.
|
196
|
+
@elements[position] = element
|
197
|
+
add_operation(:type => Operation::DOCUMENT_ELEMENT_REPLACE, :index => position, :property => element)
|
198
|
+
|
199
|
+
element
|
200
|
+
end
|
201
|
+
|
202
|
+
# Appends an element
|
203
|
+
def append_element(element)
|
204
|
+
# TODO: What happens if there already is an element at end of content?
|
205
|
+
@elements[@content.length] = element
|
206
|
+
add_operation(:type => Operation::DOCUMENT_ELEMENT_APPEND, :property => element)
|
207
|
+
|
208
|
+
element
|
209
|
+
end
|
210
|
+
|
211
|
+
protected
|
212
|
+
def add_operation(options) # :nodoc:
|
213
|
+
@context.add_operation(options.merge(:blip_id => @id, :wavelet_id => @wavelet_id, :wave_id => @wave_id))
|
214
|
+
end
|
215
|
+
|
216
|
+
# Strips all HTML tags from a string, returning what it would look like unformatted.
|
217
|
+
def strip_html_tags(text) # :nodoc:
|
218
|
+
# Replace existing newlines/tabs with spaces, since they don't affect layout.
|
219
|
+
str = text.gsub(/[\n\t]/, ' ')
|
220
|
+
# Replace all <br /> with a newline.
|
221
|
+
str.gsub!(/<br\s*\/>\s*/, "\n")
|
222
|
+
# Put newline where are </h?>, </p> </div>, unless at the end.
|
223
|
+
str.gsub!(/<\/(?:h\d|p|div)>\s*(?!$)/, "\n")
|
224
|
+
# Remove all tags.
|
225
|
+
str.gsub!(/<\/?[^<]*>/, '')
|
226
|
+
# Remove spaces at each end.
|
227
|
+
str.gsub!(/^ +| +$/, '')
|
228
|
+
# Compress all adjacent spaces into a single space.
|
229
|
+
str.gsub(/ {2,}/, ' ')
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|