collab 0.2.0 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/jobs/collab/commit_job.rb +7 -0
- data/lib/collab.rb +6 -10
- data/lib/collab/channel.rb +11 -10
- data/lib/collab/config.rb +30 -1
- data/lib/collab/engine.rb +6 -0
- data/lib/collab/has_collaborative_document.rb +1 -1
- data/lib/collab/js.rb +93 -0
- data/lib/collab/models/base.rb +1 -1
- data/lib/collab/models/commit.rb +53 -0
- data/lib/collab/models/document.rb +24 -45
- data/lib/collab/version.rb +1 -1
- data/lib/generators/collab/install/install_generator.rb +3 -1
- data/lib/generators/collab/install/templates/channel.rb +9 -6
- data/lib/generators/collab/install/templates/create_collab_tables.rb.erb +28 -0
- data/lib/generators/collab/install/templates/initializer.rb +13 -18
- metadata +7 -7
- data/app/jobs/collab/document_transaction_job.rb +0 -7
- data/lib/collab/bridge.rb +0 -54
- data/lib/collab/models/document_transaction.rb +0 -22
- data/lib/collab/railtie.rb +0 -4
- data/lib/generators/collab/install/templates/create_collab_tables.rb +0 -32
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f8c6f4ca46b51f7fd05bacdea192bbd5ec7979b6c44f7528665beda336fb6d43
|
4
|
+
data.tar.gz: e2a0748fe7e1686a36a7ace934153433b1a499c18f8a0c930ed689564f9eb90e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36feda3ab2009c1a954434893a1ca89eed1f9492b08a20af7872d720abceaf4351398b16a9c6ba63cccf76f318ba387b3be29c85cf0616aba52a20f8fed7f71c
|
7
|
+
data.tar.gz: bd79ab6de7c2c24185f581031d4b25f673d5ef037f2f3794d4b06cb2b2906c772227d8ad9aa3ec4b63b28d82f0a48f2fdd3a54f6835db919b67b628b0c83fdb0
|
data/lib/collab.rb
CHANGED
@@ -1,19 +1,15 @@
|
|
1
|
-
require "collab/
|
1
|
+
require "collab/version"
|
2
|
+
require "collab/config"
|
3
|
+
require "collab/js"
|
4
|
+
require "collab/engine"
|
2
5
|
|
3
6
|
module Collab
|
4
|
-
|
5
|
-
@config ||= Collab::Config.new
|
6
|
-
return @config unless block_given?
|
7
|
-
yield @config
|
8
|
-
end
|
9
|
-
|
10
|
-
autoload "Config", "collab/config"
|
11
|
-
autoload "Bridge", "collab/bridge"
|
7
|
+
autoload "Channel", "collab/channel"
|
12
8
|
autoload "HasCollaborativeDocument", "collab/has_collaborative_document"
|
13
9
|
|
14
10
|
module Models
|
15
11
|
autoload "Base", "collab/models/base"
|
16
12
|
autoload "Document", "collab/models/document"
|
17
|
-
autoload "
|
13
|
+
autoload "Commit", "collab/models/commit"
|
18
14
|
end
|
19
15
|
end
|
data/lib/collab/channel.rb
CHANGED
@@ -10,19 +10,20 @@ module Collab
|
|
10
10
|
|
11
11
|
stream_for document
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
commits = document.commits
|
14
|
+
.where("document_version > ?", starting_version)
|
15
|
+
.order(document_version: :asc)
|
16
|
+
.load
|
17
17
|
|
18
|
-
|
19
|
-
|
20
|
-
|
18
|
+
unless commits.empty?
|
19
|
+
raise "invalid version" unless commits.first.document_version == (starting_version + 1)
|
20
|
+
commits.lazy.map(&:as_json).each(method(:transmit))
|
21
|
+
end
|
21
22
|
end
|
22
23
|
|
23
|
-
def
|
24
|
-
|
25
|
-
document.
|
24
|
+
def commit(data)
|
25
|
+
authorize_commit!(data)
|
26
|
+
document.commit_later(data)
|
26
27
|
end
|
27
28
|
|
28
29
|
def unsubscribed
|
data/lib/collab/config.rb
CHANGED
@@ -1,5 +1,34 @@
|
|
1
1
|
module Collab
|
2
|
+
@config_mutex = Mutex.new
|
3
|
+
|
4
|
+
def self.config
|
5
|
+
if block_given?
|
6
|
+
@config_mutex.synchronize do
|
7
|
+
@config ||= ::Collab::Config.new
|
8
|
+
raise "[Collab] Tried to configure gem after first use" if @config.frozen?
|
9
|
+
yield @config
|
10
|
+
end
|
11
|
+
else
|
12
|
+
raise "[Collab] Missing configuration - Have you run `rails g collab:install` yet?" unless @config
|
13
|
+
@config.freeze # really weird stuff could happen if the config changes after first use, so freeze config
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
2
17
|
class Config
|
3
|
-
attr_accessor :
|
18
|
+
attr_accessor :base_record,
|
19
|
+
:base_job,
|
20
|
+
:channel,
|
21
|
+
:commit_job,
|
22
|
+
:commit_model,
|
23
|
+
:document_model,
|
24
|
+
:max_commit_history_length,
|
25
|
+
:num_js_processes,
|
26
|
+
:schema_package
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
self.commit_job = "Collab::CommitJob"
|
30
|
+
self.document_model = "Collab::Models::Document"
|
31
|
+
self.commit_model = "Collab::Models::Commit"
|
32
|
+
end
|
4
33
|
end
|
5
34
|
end
|
@@ -8,7 +8,7 @@ module Collab
|
|
8
8
|
|
9
9
|
define_method attach_as do
|
10
10
|
super() || begin
|
11
|
-
document = self.__send__("build_#{attach_as}", schema_name: schema,
|
11
|
+
document = self.__send__("build_#{attach_as}", schema_name: schema, content: blank_document.dup)
|
12
12
|
document.save!
|
13
13
|
document
|
14
14
|
end
|
data/lib/collab/js.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require "json"
|
2
|
+
|
3
|
+
module Collab
|
4
|
+
module JS
|
5
|
+
@queue = Queue.new
|
6
|
+
@queue_initialized = false
|
7
|
+
@queue_initialization_mutex = Mutex.new
|
8
|
+
|
9
|
+
class <<self
|
10
|
+
def queue
|
11
|
+
initialize_queue unless @queue_initialized
|
12
|
+
@queue
|
13
|
+
end
|
14
|
+
|
15
|
+
# Calls the block given with a JS process acquired from the queue
|
16
|
+
# Will block until a JS process is available
|
17
|
+
def with_js
|
18
|
+
js = queue.pop
|
19
|
+
yield js
|
20
|
+
ensure
|
21
|
+
queue << js
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(name, data = nil, schema_name:)
|
25
|
+
with_js { |js| js.call(name, *arguments, &block) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def apply_commit(document, commit, schema_name:)
|
29
|
+
call("applyCommit", {doc: document, commit: commit}, schema_name: schema_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
def html_to_document(html, schema_name:)
|
33
|
+
call("htmlToDoc", html, schema_name: schema_name)
|
34
|
+
end
|
35
|
+
|
36
|
+
def document_to_html(document, schema_name:)
|
37
|
+
call("docToHtml", document, schema_name: schema_name)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
# Thread-safe initialization of the NodeJS process queue
|
42
|
+
def initialize_queue
|
43
|
+
@queue_initialization_mutex.synchronize do
|
44
|
+
unless @queue_initialized
|
45
|
+
::Collab.config.num_js_processes.times { @queue << ::Collab::JS::JSProcess.new }
|
46
|
+
@queue_initialized = true
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
class JSProcess
|
53
|
+
def initialize
|
54
|
+
@node = if defined?(Rails)
|
55
|
+
Dir.chdir(Rails.root) { open_node }
|
56
|
+
else
|
57
|
+
open_node
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def call(name, data = nil, schema_name:)
|
62
|
+
req = {name: name, data: data, schemaPackage: ::Collab.config.schema_package, schemaName: schema_name}
|
63
|
+
@node.puts(JSON.generate(req))
|
64
|
+
res = JSON.parse(@node.gets)
|
65
|
+
raise ::Collab::JS::JSRuntimeError.new(res["error"]) if res["error"]
|
66
|
+
res["result"]
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def open_node
|
71
|
+
IO.popen(["node", "-e", "require('rails-collab-server')"], "r+")
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
class JSRuntimeError < StandardError
|
76
|
+
def initialize(data)
|
77
|
+
@js_backtrace = data["stack"].split("\n").map{|f| "JavaScript #{f.strip}"} if data["stack"]
|
78
|
+
|
79
|
+
super(data["name"] + ": " + data["message"])
|
80
|
+
end
|
81
|
+
|
82
|
+
def backtrace
|
83
|
+
return unless val = super
|
84
|
+
|
85
|
+
if @js_backtrace
|
86
|
+
@js_backtrace + val
|
87
|
+
else
|
88
|
+
val
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/collab/models/base.rb
CHANGED
@@ -0,0 +1,53 @@
|
|
1
|
+
module Collab
|
2
|
+
class Models::Commit < ::Collab::Models::Base
|
3
|
+
belongs_to :document, class_name: ::Collab.config.document_model
|
4
|
+
|
5
|
+
validates :steps, presence: true
|
6
|
+
validates :document_version, presence: true
|
7
|
+
validates :ref, length: { maximum: 36 }
|
8
|
+
|
9
|
+
after_create_commit :broadcast
|
10
|
+
|
11
|
+
def self.from_json(data)
|
12
|
+
new(document_version: data["v"]&.to_i, steps: data["steps"], ref: data["ref"])
|
13
|
+
end
|
14
|
+
|
15
|
+
def as_json
|
16
|
+
{
|
17
|
+
v: document_version,
|
18
|
+
steps: steps,
|
19
|
+
ref: ref
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
def apply_later
|
24
|
+
raise "cannot apply persisted commit" if self.persisted?
|
25
|
+
raise "commit not valid" unless self.valid?
|
26
|
+
return false if self.document.document_version != self.document_version
|
27
|
+
|
28
|
+
::Collab.config.commit_job.constantize.perform_later(self.document, as_json)
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply!
|
32
|
+
raise "cannot apply persisted commit" if self.persisted?
|
33
|
+
raise "commit not valid" unless self.valid?
|
34
|
+
return false if self.document.document_version != self.document_version # optimization, prevents need to apply lock if outdated
|
35
|
+
|
36
|
+
self.document.with_lock do
|
37
|
+
return false if self.document.document_version != self.document_version
|
38
|
+
|
39
|
+
return false unless result = ::Collab::JS.apply_commit(self.document, self.to_json, schema_name: self.document.schema_name)
|
40
|
+
|
41
|
+
self.document.document = result["doc"]
|
42
|
+
self.document.document_version = self.document_version
|
43
|
+
|
44
|
+
self.document.save!
|
45
|
+
self.save!
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def broadcast
|
50
|
+
::Collab.config.channel_name.constantize.broadcast_to(document, as_json)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,73 +1,52 @@
|
|
1
1
|
module Collab
|
2
2
|
class Models::Document < ::Collab::Models::Base
|
3
3
|
belongs_to :attached, polymorphic: true
|
4
|
-
has_many :
|
4
|
+
has_many :commits, class_name: ::Collab.config.commit_model, foreign_key: :document_id
|
5
5
|
|
6
|
-
validates :
|
6
|
+
validates :content, presence: true
|
7
7
|
validates :document_version, presence: true, numericality: {only_integer: true, greater_than_or_equal_to: 0}
|
8
8
|
validates :schema_name, presence: true
|
9
9
|
|
10
|
-
|
11
|
-
after_save :delete_old_transactions
|
10
|
+
after_save :delete_old_commits
|
12
11
|
|
13
|
-
#
|
14
|
-
|
15
|
-
return super if serialized_html_fresh?
|
16
|
-
end
|
17
|
-
|
18
|
-
def serialized_html_fresh?
|
19
|
-
serialized_html_version == document_version
|
20
|
-
end
|
21
|
-
|
22
|
-
# Serialize the document to html - will be cached if possible (somewhat expensive)
|
12
|
+
# Serialize the document to html, uses cached if possible.
|
13
|
+
# Note that this may lock the document
|
23
14
|
def to_html
|
24
15
|
return serialized_html if serialized_html
|
25
16
|
|
26
|
-
|
27
|
-
self.
|
28
|
-
|
17
|
+
serialized_version = self.document_version
|
18
|
+
::Collab::JS.document_to_html(self.content, schema_name: schema_name).tap do |serialized_html|
|
19
|
+
Thread.new do # use a thread to prevent deadlocks and avoid incuring the cost of an inline-write
|
20
|
+
self.with_lock do
|
21
|
+
self.update_attribute(:serialized_html, serialized_html) if serialized_version == self.version and self.serialized_html.nil?
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
29
25
|
end
|
30
26
|
|
31
27
|
def from_html(html)
|
32
|
-
self.
|
33
|
-
end
|
34
|
-
|
35
|
-
def perform_transaction_later(data)
|
36
|
-
::Collab::DocumentTransactionJob.perform_later(@document, data)
|
28
|
+
self.content = ::Collab::JS.html_to_document(html, schema_name: schema_name)
|
37
29
|
end
|
38
30
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
-
return if (data["v"] - 1) != self.document_version # if expired between queue and perform, there is no need to aquire a lock. this is an optimization - not a guarantee
|
43
|
-
with_lock do
|
44
|
-
return if (data["v"] - 1) != self.document_version # now that we've aquired a lock to the record, ensure that we're still accessing the correct version
|
45
|
-
|
46
|
-
transaction_result = ::Collab::Bridge.current.apply_transaction(self.document, data, schema_name: self.schema_name)
|
47
|
-
return unless transaction_result # check to make sure the transaction succeeded
|
48
|
-
|
49
|
-
self.document = transaction_result["doc"]
|
50
|
-
self.document_version = data["v"]
|
51
|
-
save!
|
52
|
-
|
53
|
-
transactions.create! document_version: self.document_version, steps: data["steps"], ref: data["ref"]
|
54
|
-
end
|
31
|
+
def commit_later(data)
|
32
|
+
commits.from_json(data).apply_later
|
55
33
|
end
|
56
34
|
|
57
35
|
def as_json
|
58
|
-
{id: id,
|
36
|
+
{id: id, content: content, version: document_version}
|
59
37
|
end
|
60
38
|
|
61
|
-
|
62
|
-
|
63
|
-
def nullify_serialized_html
|
39
|
+
def content_will_change!
|
40
|
+
super
|
64
41
|
self.serialized_html = nil
|
65
42
|
end
|
66
43
|
|
67
|
-
|
68
|
-
|
44
|
+
private
|
45
|
+
|
46
|
+
def delete_old_commits
|
47
|
+
cutoff = document_version - ::Collab.config.max_commit_history_length
|
69
48
|
return if cutoff <= 0
|
70
|
-
|
49
|
+
commits.where("document_version < ?", cutoff).delete_all
|
71
50
|
end
|
72
51
|
end
|
73
52
|
end
|
data/lib/collab/version.rb
CHANGED
@@ -17,8 +17,10 @@ module Collab
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def copy_files
|
20
|
+
@primary_key_type = Rails.application.config.generators.active_record[:primary_key_type]
|
21
|
+
|
20
22
|
migration_template(
|
21
|
-
"create_collab_tables.rb",
|
23
|
+
"create_collab_tables.rb.erb",
|
22
24
|
"db/migrate/create_collab_tables.rb",
|
23
25
|
)
|
24
26
|
|
@@ -4,16 +4,19 @@ class CollabDocumentChannel < ApplicationCable::Channel
|
|
4
4
|
private
|
5
5
|
|
6
6
|
# Find the document to subscribe to based on the params passed to the channel
|
7
|
-
# Authorization may also be performed here (raise an error)
|
7
|
+
# Authorization may also be performed here (raise an error to prevent subscription)
|
8
8
|
def find_document
|
9
9
|
Collab::Models::Document.find(params[:document_id]).tap do |document|
|
10
|
-
|
10
|
+
# TODO: Replace with your own authorization logic
|
11
|
+
raise "authorization not implemented"
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
# Called
|
15
|
-
#
|
16
|
-
|
17
|
-
|
15
|
+
# Called a commit is first received for processing
|
16
|
+
# Throw an error to prevent the commit from being processed
|
17
|
+
# You should consider adding some type of rate-limiting here
|
18
|
+
def authorize_commit!(data)
|
19
|
+
# TODO: Replace with your own authorization logic
|
20
|
+
raise "authorization not implemented"
|
18
21
|
end
|
19
22
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class CreateCollabTables < ActiveRecord::Migration[6.0]
|
2
|
+
def change
|
3
|
+
create_table :collab_documents<%= ", id: #{@primary_key_type.inspect}" if @primary_key_type %> do |t|
|
4
|
+
t.references :attached, null: false, index: false, polymorphic: true<%= ", type: #{@primary_key_type.inspect}" if @primary_key_type %>
|
5
|
+
t.string :attached_as, null: false
|
6
|
+
|
7
|
+
t.index [:attached_type, :attached_id, :attached_as], name: "index_collab_documents_on_attached"
|
8
|
+
|
9
|
+
t.jsonb :content, null: false
|
10
|
+
t.string :schema_name, null: false
|
11
|
+
t.integer :document_version, null: false, default: 0
|
12
|
+
t.text :serialized_html
|
13
|
+
|
14
|
+
t.timestamps
|
15
|
+
end
|
16
|
+
|
17
|
+
create_table :collab_commits, id: false do |t|
|
18
|
+
t.references :document, null: false, foreign_key: {to_table: :collab_documents}, index: false<%= ", type: #{@primary_key_type.inspect}" if @primary_key_type %>
|
19
|
+
t.integer :document_version, null: false
|
20
|
+
t.index [:document_id, :document_version], unique: true, order: {document_version: :asc}, name: "index_collab_commits"
|
21
|
+
|
22
|
+
t.jsonb :steps, array: true, null: false
|
23
|
+
t.string :ref
|
24
|
+
|
25
|
+
t.datetime :created_at, null: false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -4,27 +4,22 @@ Collab.config do |c|
|
|
4
4
|
# To use a Git repo, see https://docs.npmjs.com/files/package.json#git-urls-as-dependencies
|
5
5
|
c.schema_package = "prosemirror-schema-basic"
|
6
6
|
# How many old transactions to keep per document
|
7
|
-
c.
|
7
|
+
c.max_commit_history_length = 250
|
8
|
+
# How many NodeJS child processes to run (shared among all threads)
|
9
|
+
c.num_js_processes = 3
|
8
10
|
|
9
|
-
# ActionCable settings
|
10
|
-
# ====================
|
11
11
|
# The document channel to use for collaboration
|
12
|
-
# If you change this, you must pass {channel: "[ChannelName]"}
|
13
|
-
c.
|
12
|
+
# If you change this, you must pass the value as {channel: "[ChannelName]"} in the params from the ActionCable client
|
13
|
+
c.channel = "CollabDocumentChannel"
|
14
14
|
|
15
|
-
#
|
16
|
-
|
17
|
-
# The
|
18
|
-
c.
|
19
|
-
# The job queue to use for DocumentTransaction jobs
|
20
|
-
c.queue_document_transaction_job_as = :default
|
15
|
+
# The class which jobs in the gem should inherit from
|
16
|
+
c.base_job = "ApplicationJob"
|
17
|
+
# The jobs to use, if you want to implement your own jobs
|
18
|
+
# c.commit_job = "..."
|
21
19
|
|
22
|
-
|
23
|
-
# ActiveRecord settings
|
24
|
-
# =====================
|
25
20
|
# The class which models in the gem should inherit from
|
26
|
-
c.
|
27
|
-
#
|
28
|
-
c.document_model = "
|
29
|
-
c.
|
21
|
+
c.base_record = "ApplicationRecord"
|
22
|
+
# The models to use, if you want to implement your own models
|
23
|
+
# c.document_model = "..."
|
24
|
+
# c.commit_model = "..."
|
30
25
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: collab
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ben Aubin
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-08-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -52,20 +52,20 @@ extensions: []
|
|
52
52
|
extra_rdoc_files: []
|
53
53
|
files:
|
54
54
|
- Rakefile
|
55
|
-
- app/jobs/collab/
|
55
|
+
- app/jobs/collab/commit_job.rb
|
56
56
|
- lib/collab.rb
|
57
|
-
- lib/collab/bridge.rb
|
58
57
|
- lib/collab/channel.rb
|
59
58
|
- lib/collab/config.rb
|
59
|
+
- lib/collab/engine.rb
|
60
60
|
- lib/collab/has_collaborative_document.rb
|
61
|
+
- lib/collab/js.rb
|
61
62
|
- lib/collab/models/base.rb
|
63
|
+
- lib/collab/models/commit.rb
|
62
64
|
- lib/collab/models/document.rb
|
63
|
-
- lib/collab/models/document_transaction.rb
|
64
|
-
- lib/collab/railtie.rb
|
65
65
|
- lib/collab/version.rb
|
66
66
|
- lib/generators/collab/install/install_generator.rb
|
67
67
|
- lib/generators/collab/install/templates/channel.rb
|
68
|
-
- lib/generators/collab/install/templates/create_collab_tables.rb
|
68
|
+
- lib/generators/collab/install/templates/create_collab_tables.rb.erb
|
69
69
|
- lib/generators/collab/install/templates/initializer.rb
|
70
70
|
- lib/tasks/collab_tasks.rake
|
71
71
|
homepage: https://github.com/benaubin/rails-collab
|
data/lib/collab/bridge.rb
DELETED
@@ -1,54 +0,0 @@
|
|
1
|
-
require "collab/config"
|
2
|
-
require "json"
|
3
|
-
|
4
|
-
module Collab
|
5
|
-
class Bridge
|
6
|
-
class JSRuntimeError < StandardError
|
7
|
-
def initialize(data)
|
8
|
-
@js_backtrace = data["stack"].split("\n").map{|f| "JavaScript #{f.strip}"} if data["stack"]
|
9
|
-
|
10
|
-
super(data["name"] + ": " + data["message"])
|
11
|
-
end
|
12
|
-
|
13
|
-
def backtrace
|
14
|
-
return unless val = super
|
15
|
-
|
16
|
-
if @js_backtrace
|
17
|
-
@js_backtrace + val
|
18
|
-
else
|
19
|
-
val
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
def initialize
|
25
|
-
@node = Dir.chdir(Rails.root) do
|
26
|
-
IO.popen(["node", "-e", "require('rails-collab-server')"], "r+")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.current
|
31
|
-
@current ||= new
|
32
|
-
end
|
33
|
-
|
34
|
-
def call(name, data = nil, schema_name:)
|
35
|
-
req = {name: name, data: data, schemaPackage: ::Collab.config.schema_package, schemaName: schema_name}
|
36
|
-
@node.puts(JSON.generate(req))
|
37
|
-
res = JSON.parse(@node.gets)
|
38
|
-
raise ::Collab::Bridge::JSRuntimeError.new(res["error"]) if res["error"]
|
39
|
-
res["result"]
|
40
|
-
end
|
41
|
-
|
42
|
-
def apply_transaction(document, transaction, schema_name:)
|
43
|
-
call("applyTransaction", {doc: document, data: transaction}, schema_name: schema_name)
|
44
|
-
end
|
45
|
-
|
46
|
-
def html_to_document(html, schema_name:)
|
47
|
-
call("htmlToDoc", html, schema_name: schema_name)
|
48
|
-
end
|
49
|
-
|
50
|
-
def document_to_html(document, schema_name:)
|
51
|
-
call("docToHtml", document, schema_name: schema_name)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Collab
|
2
|
-
class Models::DocumentTransaction < ::Collab::Models::Base
|
3
|
-
belongs_to :document, class_name: ::Collab.config.document_model
|
4
|
-
|
5
|
-
validates :steps, presence: true
|
6
|
-
validates :document_version, presence: true
|
7
|
-
|
8
|
-
after_create_commit :broadcast
|
9
|
-
|
10
|
-
def as_json
|
11
|
-
{
|
12
|
-
v: document_version,
|
13
|
-
steps: steps,
|
14
|
-
ref: ref
|
15
|
-
}
|
16
|
-
end
|
17
|
-
|
18
|
-
def broadcast
|
19
|
-
::Collab.config.channel_name.constantize.broadcast_to(document, as_json)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
data/lib/collab/railtie.rb
DELETED
@@ -1,32 +0,0 @@
|
|
1
|
-
class CreateCollabTables < ActiveRecord::Migration[6.0]
|
2
|
-
def change
|
3
|
-
create_table :collab_documents, id: :uuid do |t|
|
4
|
-
t.references :attached, null: false, index: false, type: :uuid, polymorphic: true
|
5
|
-
t.string :attached_as
|
6
|
-
t.index [:attached_type, :attached_id, :attached_as], name: "index_collaborative_documents_on_attached"
|
7
|
-
|
8
|
-
t.string :schema_name, null: false
|
9
|
-
|
10
|
-
t.jsonb :document, null: false
|
11
|
-
t.integer :document_version, null: false, default: 0
|
12
|
-
|
13
|
-
t.text :serialized_html
|
14
|
-
t.integer :serialized_html_version
|
15
|
-
|
16
|
-
t.timestamps
|
17
|
-
end
|
18
|
-
|
19
|
-
create_table :collab_document_transactions, id: false do |t|
|
20
|
-
t.references :document, null: false, foreign_key: {to_table: :collab_documents}, type: :uuid, index: false
|
21
|
-
|
22
|
-
t.jsonb :steps, array: true, null: false
|
23
|
-
t.integer :document_version, null: false
|
24
|
-
|
25
|
-
t.string :ref
|
26
|
-
|
27
|
-
t.index [:document_id, :document_version], unique: true, order: {document_version: :asc}, name: "index_collaborative_document_transactions"
|
28
|
-
|
29
|
-
t.timestamps
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|