collab 0.2.0 → 0.3.1
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.
- 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
|