docsmith 0.1.0
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 +7 -0
- data/.rspec +3 -0
- data/.rspec_status +212 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +66 -0
- data/Rakefile +8 -0
- data/USAGE.md +510 -0
- data/docs/superpowers/plans/2026-04-01-docsmith-full-plan.md +6459 -0
- data/docs/superpowers/plans/2026-04-08-parsers-remove-branches-docs.md +2112 -0
- data/docs/superpowers/specs/2026-04-01-docsmith-phase1-design.md +340 -0
- data/docsmith_spec.md +630 -0
- data/lib/docsmith/auto_save.rb +29 -0
- data/lib/docsmith/comments/anchor.rb +68 -0
- data/lib/docsmith/comments/comment.rb +44 -0
- data/lib/docsmith/comments/manager.rb +73 -0
- data/lib/docsmith/comments/migrator.rb +64 -0
- data/lib/docsmith/configuration.rb +95 -0
- data/lib/docsmith/diff/engine.rb +39 -0
- data/lib/docsmith/diff/parsers/html.rb +64 -0
- data/lib/docsmith/diff/parsers/markdown.rb +60 -0
- data/lib/docsmith/diff/renderers/base.rb +62 -0
- data/lib/docsmith/diff/renderers/registry.rb +41 -0
- data/lib/docsmith/diff/renderers.rb +10 -0
- data/lib/docsmith/diff/result.rb +77 -0
- data/lib/docsmith/diff.rb +6 -0
- data/lib/docsmith/document.rb +44 -0
- data/lib/docsmith/document_version.rb +50 -0
- data/lib/docsmith/errors.rb +18 -0
- data/lib/docsmith/events/event.rb +19 -0
- data/lib/docsmith/events/hook_registry.rb +14 -0
- data/lib/docsmith/events/notifier.rb +22 -0
- data/lib/docsmith/rendering/html_renderer.rb +36 -0
- data/lib/docsmith/rendering/json_renderer.rb +29 -0
- data/lib/docsmith/version.rb +5 -0
- data/lib/docsmith/version_manager.rb +143 -0
- data/lib/docsmith/version_tag.rb +25 -0
- data/lib/docsmith/versionable.rb +252 -0
- data/lib/docsmith.rb +52 -0
- data/lib/generators/docsmith/install/install_generator.rb +27 -0
- data/lib/generators/docsmith/install/templates/create_docsmith_tables.rb.erb +64 -0
- data/lib/generators/docsmith/install/templates/docsmith_initializer.rb.erb +19 -0
- data/sig/docsmith.rbs +4 -0
- metadata +196 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Docsmith
|
|
4
|
+
# ActiveRecord mixin that adds full versioning to any model.
|
|
5
|
+
#
|
|
6
|
+
# Usage:
|
|
7
|
+
# class Article < ApplicationRecord
|
|
8
|
+
# include Docsmith::Versionable
|
|
9
|
+
# docsmith_config { content_field :body; content_type :markdown }
|
|
10
|
+
# end
|
|
11
|
+
module Versionable
|
|
12
|
+
def self.included(base)
|
|
13
|
+
base.extend(ClassMethods)
|
|
14
|
+
base.after_save(:_docsmith_auto_save_callback)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
# Configure per-class Docsmith options. All keys optional.
|
|
19
|
+
# Unset keys fall through to global config then gem defaults.
|
|
20
|
+
# @yield block evaluated on a Docsmith::ClassConfig instance
|
|
21
|
+
# @return [Docsmith::ClassConfig]
|
|
22
|
+
def docsmith_config(&block)
|
|
23
|
+
@_docsmith_class_config ||= Docsmith::ClassConfig.new
|
|
24
|
+
@_docsmith_class_config.instance_eval(&block) if block_given?
|
|
25
|
+
@_docsmith_class_config
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Hash] fully resolved config (read-time resolution)
|
|
29
|
+
def docsmith_resolved_config
|
|
30
|
+
Docsmith::Configuration.resolve(
|
|
31
|
+
@_docsmith_class_config&.settings || {},
|
|
32
|
+
Docsmith.configuration
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Create a new DocumentVersion snapshot of this record's content.
|
|
38
|
+
# Returns nil if content is identical to the latest version.
|
|
39
|
+
# Raises Docsmith::InvalidContentField if content_field returns a non-String
|
|
40
|
+
# and no content_extractor is configured.
|
|
41
|
+
#
|
|
42
|
+
# @param author [Object, nil]
|
|
43
|
+
# @param summary [String, nil]
|
|
44
|
+
# @return [Docsmith::DocumentVersion, nil]
|
|
45
|
+
def save_version!(author:, summary: nil)
|
|
46
|
+
_sync_docsmith_content!
|
|
47
|
+
Docsmith::VersionManager.save!(
|
|
48
|
+
_docsmith_document,
|
|
49
|
+
author: author,
|
|
50
|
+
summary: summary,
|
|
51
|
+
config: self.class.docsmith_resolved_config
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Debounced auto-save. Returns nil if debounce window has not elapsed
|
|
56
|
+
# OR content is unchanged. Both non-save cases return nil.
|
|
57
|
+
# auto_save: false in config causes this to always return nil.
|
|
58
|
+
#
|
|
59
|
+
# @param author [Object, nil]
|
|
60
|
+
# @return [Docsmith::DocumentVersion, nil]
|
|
61
|
+
def auto_save_version!(author: nil)
|
|
62
|
+
config = self.class.docsmith_resolved_config
|
|
63
|
+
return nil unless config[:auto_save]
|
|
64
|
+
|
|
65
|
+
_sync_docsmith_content!
|
|
66
|
+
Docsmith::AutoSave.call(_docsmith_document, author: author, config: config)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @return [ActiveRecord::Relation<Docsmith::DocumentVersion>] ordered by version_number
|
|
70
|
+
def versions
|
|
71
|
+
_docsmith_document.document_versions
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Docsmith::DocumentVersion, nil] latest version
|
|
75
|
+
def current_version
|
|
76
|
+
_docsmith_document.current_version
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# @param number [Integer] 1-indexed version_number
|
|
80
|
+
# @return [Docsmith::DocumentVersion, nil]
|
|
81
|
+
def version(number)
|
|
82
|
+
_docsmith_document.document_versions.find_by(version_number: number)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Restore to a previous version. Creates a new version with the old content.
|
|
86
|
+
# Syncs restored content back to the model's content_field via update_column
|
|
87
|
+
# (bypasses after_save to prevent a duplicate auto-save).
|
|
88
|
+
# Never mutates existing versions.
|
|
89
|
+
#
|
|
90
|
+
# @param number [Integer] version_number to restore from
|
|
91
|
+
# @param author [Object, nil]
|
|
92
|
+
# @return [Docsmith::DocumentVersion]
|
|
93
|
+
# @raise [Docsmith::VersionNotFound]
|
|
94
|
+
def restore_version!(number, author:)
|
|
95
|
+
result = Docsmith::VersionManager.restore!(
|
|
96
|
+
_docsmith_document,
|
|
97
|
+
version: number,
|
|
98
|
+
author: author,
|
|
99
|
+
config: self.class.docsmith_resolved_config
|
|
100
|
+
)
|
|
101
|
+
field = self.class.docsmith_resolved_config[:content_field]
|
|
102
|
+
update_column(field, _docsmith_document.reload.content)
|
|
103
|
+
result
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Tag a specific version. Names are unique per document.
|
|
107
|
+
# @param number [Integer] version_number to tag
|
|
108
|
+
# @param name [String]
|
|
109
|
+
# @param author [Object, nil]
|
|
110
|
+
# @return [Docsmith::VersionTag]
|
|
111
|
+
def tag_version!(number, name:, author:)
|
|
112
|
+
Docsmith::VersionManager.tag!(
|
|
113
|
+
_docsmith_document, version: number, name: name, author: author)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @param tag_name [String]
|
|
117
|
+
# @return [Docsmith::DocumentVersion, nil]
|
|
118
|
+
def tagged_version(tag_name)
|
|
119
|
+
tag = _docsmith_document.version_tags.find_by(name: tag_name)
|
|
120
|
+
tag&.version
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# @param number [Integer] version_number
|
|
124
|
+
# @return [Array<String>] tag names on that version
|
|
125
|
+
def version_tags(number)
|
|
126
|
+
ver = version(number)
|
|
127
|
+
return [] unless ver
|
|
128
|
+
ver.version_tags.pluck(:name)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Computes a diff from version N to the current (latest) version.
|
|
132
|
+
#
|
|
133
|
+
# @param version_number [Integer]
|
|
134
|
+
# @return [Docsmith::Diff::Result]
|
|
135
|
+
# @raise [ActiveRecord::RecordNotFound] if version_number does not exist
|
|
136
|
+
def diff_from(version_number)
|
|
137
|
+
doc = _docsmith_document
|
|
138
|
+
v_from = Docsmith::DocumentVersion.find_by!(document: doc, version_number: version_number)
|
|
139
|
+
v_to = Docsmith::DocumentVersion.where(document_id: doc.id).order(version_number: :desc).first!
|
|
140
|
+
Docsmith::Diff.between(v_from, v_to)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Computes a diff between two named versions.
|
|
144
|
+
#
|
|
145
|
+
# @param from_version [Integer]
|
|
146
|
+
# @param to_version [Integer]
|
|
147
|
+
# @return [Docsmith::Diff::Result]
|
|
148
|
+
# @raise [ActiveRecord::RecordNotFound] if either version does not exist
|
|
149
|
+
def diff_between(from_version, to_version)
|
|
150
|
+
doc = _docsmith_document
|
|
151
|
+
v_from = Docsmith::DocumentVersion.find_by!(document: doc, version_number: from_version)
|
|
152
|
+
v_to = Docsmith::DocumentVersion.find_by!(document: doc, version_number: to_version)
|
|
153
|
+
Docsmith::Diff.between(v_from, v_to)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Adds a comment to a specific version of this document.
|
|
157
|
+
#
|
|
158
|
+
# @param version [Integer] version_number
|
|
159
|
+
# @param body [String]
|
|
160
|
+
# @param author [Object] polymorphic author
|
|
161
|
+
# @param anchor [Hash, nil] { start_offset:, end_offset: } for inline range comments
|
|
162
|
+
# @param parent [Comments::Comment, nil] parent comment for threading
|
|
163
|
+
# @return [Docsmith::Comments::Comment]
|
|
164
|
+
def add_comment!(version:, body:, author:, anchor: nil, parent: nil)
|
|
165
|
+
Comments::Manager.add!(
|
|
166
|
+
_docsmith_document,
|
|
167
|
+
version_number: version,
|
|
168
|
+
body: body,
|
|
169
|
+
author: author,
|
|
170
|
+
anchor: anchor,
|
|
171
|
+
parent: parent
|
|
172
|
+
)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
# Returns all comments across all versions of this document.
|
|
176
|
+
#
|
|
177
|
+
# @return [ActiveRecord::Relation<Docsmith::Comments::Comment>]
|
|
178
|
+
def comments
|
|
179
|
+
doc = _docsmith_document
|
|
180
|
+
Comments::Comment.joins(:version)
|
|
181
|
+
.where(docsmith_versions: { document_id: doc.id })
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
# Returns comments on a specific version, optionally filtered by anchor type.
|
|
185
|
+
#
|
|
186
|
+
# @param version [Integer] version_number
|
|
187
|
+
# @param type [Symbol, nil] :document or :range to filter; nil = all
|
|
188
|
+
# @return [ActiveRecord::Relation<Docsmith::Comments::Comment>]
|
|
189
|
+
def comments_on(version:, type: nil)
|
|
190
|
+
doc = _docsmith_document
|
|
191
|
+
dv = Docsmith::DocumentVersion.find_by!(document: doc, version_number: version)
|
|
192
|
+
rel = Comments::Comment.where(version: dv)
|
|
193
|
+
rel = rel.where(anchor_type: type.to_s) if type
|
|
194
|
+
rel
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Returns all unresolved comments across all versions.
|
|
198
|
+
#
|
|
199
|
+
# @return [ActiveRecord::Relation<Docsmith::Comments::Comment>]
|
|
200
|
+
def unresolved_comments
|
|
201
|
+
comments.merge(Comments::Comment.unresolved)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Migrates top-level comments from one version to another.
|
|
205
|
+
#
|
|
206
|
+
# @param from [Integer] source version_number
|
|
207
|
+
# @param to [Integer] target version_number
|
|
208
|
+
# @return [void]
|
|
209
|
+
def migrate_comments!(from:, to:)
|
|
210
|
+
Comments::Migrator.migrate!(_docsmith_document, from: from, to: to)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
private
|
|
214
|
+
|
|
215
|
+
# Finds or creates the shadow Docsmith::Document for this record.
|
|
216
|
+
# Cached in @_docsmith_document after first lookup.
|
|
217
|
+
def _docsmith_document
|
|
218
|
+
config = self.class.docsmith_resolved_config
|
|
219
|
+
@_docsmith_document ||= Docsmith::Document.find_or_create_by!(subject: self) do |doc|
|
|
220
|
+
doc.content_type = config[:content_type].to_s
|
|
221
|
+
doc.title = respond_to?(:title) ? title.to_s : self.class.name
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Reads content from the model via content_extractor or content_field,
|
|
226
|
+
# validates it is a String, then syncs to the shadow document's content column.
|
|
227
|
+
def _sync_docsmith_content!
|
|
228
|
+
config = self.class.docsmith_resolved_config
|
|
229
|
+
|
|
230
|
+
raw = if config[:content_extractor]
|
|
231
|
+
config[:content_extractor].call(self)
|
|
232
|
+
else
|
|
233
|
+
public_send(config[:content_field])
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
unless raw.nil? || raw.is_a?(String)
|
|
237
|
+
source = config[:content_extractor] ? "content_extractor" : "content_field :#{config[:content_field]}"
|
|
238
|
+
raise Docsmith::InvalidContentField,
|
|
239
|
+
"#{source} must return a String, got #{raw.class}. " \
|
|
240
|
+
"Use content_extractor: ->(record) { ... } for non-string fields."
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
_docsmith_document.update_column(:content, raw.to_s)
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def _docsmith_auto_save_callback
|
|
247
|
+
auto_save_version!
|
|
248
|
+
rescue Docsmith::InvalidContentField
|
|
249
|
+
nil
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
data/lib/docsmith.rb
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "active_record"
|
|
4
|
+
require "active_support"
|
|
5
|
+
require "active_support/core_ext/numeric/time"
|
|
6
|
+
require "active_support/notifications"
|
|
7
|
+
|
|
8
|
+
require_relative "docsmith/version"
|
|
9
|
+
require_relative "docsmith/errors"
|
|
10
|
+
require_relative "docsmith/configuration"
|
|
11
|
+
require_relative "docsmith/events/event"
|
|
12
|
+
require_relative "docsmith/events/hook_registry"
|
|
13
|
+
require_relative "docsmith/events/notifier"
|
|
14
|
+
require_relative "docsmith/document"
|
|
15
|
+
require_relative "docsmith/document_version"
|
|
16
|
+
require_relative "docsmith/version_tag"
|
|
17
|
+
require_relative "docsmith/auto_save"
|
|
18
|
+
require_relative "docsmith/version_manager"
|
|
19
|
+
require_relative "docsmith/versionable"
|
|
20
|
+
require_relative "docsmith/diff"
|
|
21
|
+
require_relative "docsmith/diff/renderers"
|
|
22
|
+
require_relative "docsmith/diff/renderers/base"
|
|
23
|
+
require_relative "docsmith/diff/renderers/registry"
|
|
24
|
+
require_relative "docsmith/diff/result"
|
|
25
|
+
require_relative "docsmith/diff/parsers/markdown"
|
|
26
|
+
require_relative "docsmith/diff/parsers/html"
|
|
27
|
+
require_relative "docsmith/diff/engine"
|
|
28
|
+
require_relative "docsmith/rendering/html_renderer"
|
|
29
|
+
require_relative "docsmith/rendering/json_renderer"
|
|
30
|
+
require_relative "docsmith/comments/comment"
|
|
31
|
+
require_relative "docsmith/comments/anchor"
|
|
32
|
+
require_relative "docsmith/comments/manager"
|
|
33
|
+
require_relative "docsmith/comments/migrator"
|
|
34
|
+
|
|
35
|
+
module Docsmith
|
|
36
|
+
class << self
|
|
37
|
+
# @yield [Docsmith::Configuration]
|
|
38
|
+
def configure
|
|
39
|
+
yield configuration
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @return [Docsmith::Configuration]
|
|
43
|
+
def configuration
|
|
44
|
+
@configuration ||= Configuration.new
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Reset to gem defaults. Call in specs via config.before(:each).
|
|
48
|
+
def reset_configuration!
|
|
49
|
+
@configuration = Configuration.new
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
require "rails/generators/active_record"
|
|
5
|
+
|
|
6
|
+
module Docsmith
|
|
7
|
+
module Generators
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
include ActiveRecord::Generators::Migration
|
|
10
|
+
|
|
11
|
+
source_root File.expand_path("templates", __dir__)
|
|
12
|
+
|
|
13
|
+
desc "Creates the Docsmith migration and initializer."
|
|
14
|
+
|
|
15
|
+
def create_migration
|
|
16
|
+
migration_template(
|
|
17
|
+
"create_docsmith_tables.rb.erb",
|
|
18
|
+
"db/migrate/create_docsmith_tables.rb"
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_initializer
|
|
23
|
+
template "docsmith_initializer.rb.erb", "config/initializers/docsmith.rb"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CreateDocsmithTables < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
|
4
|
+
def change
|
|
5
|
+
create_table :docsmith_documents do |t|
|
|
6
|
+
t.string :title
|
|
7
|
+
t.text :content
|
|
8
|
+
t.string :content_type, null: false, default: "markdown"
|
|
9
|
+
t.integer :versions_count, null: false, default: 0
|
|
10
|
+
t.datetime :last_versioned_at
|
|
11
|
+
t.string :subject_type
|
|
12
|
+
t.bigint :subject_id
|
|
13
|
+
t.jsonb :metadata, null: false, default: {}
|
|
14
|
+
t.timestamps
|
|
15
|
+
end
|
|
16
|
+
add_index :docsmith_documents, %i[subject_type subject_id]
|
|
17
|
+
|
|
18
|
+
create_table :docsmith_versions do |t|
|
|
19
|
+
t.references :document, null: false, foreign_key: { to_table: :docsmith_documents }
|
|
20
|
+
t.integer :version_number, null: false
|
|
21
|
+
t.text :content, null: false
|
|
22
|
+
t.string :content_type, null: false
|
|
23
|
+
t.string :author_type
|
|
24
|
+
t.bigint :author_id
|
|
25
|
+
t.string :change_summary
|
|
26
|
+
t.jsonb :metadata, null: false, default: {}
|
|
27
|
+
t.datetime :created_at, null: false
|
|
28
|
+
end
|
|
29
|
+
add_index :docsmith_versions, %i[document_id version_number], unique: true
|
|
30
|
+
add_index :docsmith_versions, %i[author_type author_id]
|
|
31
|
+
|
|
32
|
+
create_table :docsmith_version_tags do |t|
|
|
33
|
+
t.references :document, null: false, foreign_key: { to_table: :docsmith_documents }
|
|
34
|
+
t.bigint :version_id, null: false
|
|
35
|
+
t.string :name, null: false
|
|
36
|
+
t.string :author_type
|
|
37
|
+
t.bigint :author_id
|
|
38
|
+
t.datetime :created_at, null: false
|
|
39
|
+
end
|
|
40
|
+
add_index :docsmith_version_tags, %i[document_id name], unique: true
|
|
41
|
+
add_index :docsmith_version_tags, [:version_id]
|
|
42
|
+
add_foreign_key :docsmith_version_tags, :docsmith_versions, column: :version_id
|
|
43
|
+
|
|
44
|
+
create_table :docsmith_comments do |t|
|
|
45
|
+
t.bigint :version_id, null: false
|
|
46
|
+
t.bigint :parent_id
|
|
47
|
+
t.string :author_type
|
|
48
|
+
t.bigint :author_id
|
|
49
|
+
t.text :body, null: false
|
|
50
|
+
t.string :anchor_type, null: false, default: "document"
|
|
51
|
+
t.jsonb :anchor_data, null: false, default: {}
|
|
52
|
+
t.boolean :resolved, null: false, default: false
|
|
53
|
+
t.string :resolved_by_type
|
|
54
|
+
t.bigint :resolved_by_id
|
|
55
|
+
t.datetime :resolved_at
|
|
56
|
+
t.timestamps null: false
|
|
57
|
+
end
|
|
58
|
+
add_index :docsmith_comments, :version_id
|
|
59
|
+
add_index :docsmith_comments, :parent_id
|
|
60
|
+
add_index :docsmith_comments, [:author_type, :author_id]
|
|
61
|
+
add_foreign_key :docsmith_comments, :docsmith_versions, column: :version_id
|
|
62
|
+
add_foreign_key :docsmith_comments, :docsmith_comments, column: :parent_id
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
Docsmith.configure do |config|
|
|
4
|
+
# Resolution order: per-class docsmith_config > this global config > gem defaults
|
|
5
|
+
#
|
|
6
|
+
# config.default_content_field = :body # gem default: :body
|
|
7
|
+
# config.default_content_type = :markdown # gem default: :markdown (:html, :markdown, :json)
|
|
8
|
+
# config.auto_save = true # gem default: true
|
|
9
|
+
# config.default_debounce = 30 # gem default: 30 (integer seconds)
|
|
10
|
+
# config.max_versions = nil # gem default: nil (unlimited)
|
|
11
|
+
# config.content_extractor = nil # example: ->(record) { record.body.to_html }
|
|
12
|
+
# config.table_prefix = "docsmith" # gem default: "docsmith"
|
|
13
|
+
# config.diff_context_lines = 3
|
|
14
|
+
#
|
|
15
|
+
# Event hooks (fires synchronously before AS::Notifications):
|
|
16
|
+
# config.on(:version_created) { |event| Rails.logger.info "v#{event.version.version_number} saved" }
|
|
17
|
+
# config.on(:version_restored) { |event| }
|
|
18
|
+
# config.on(:version_tagged) { |event| }
|
|
19
|
+
end
|
data/sig/docsmith.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: docsmith
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- swastik009
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-04-12 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: activerecord
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '7.0'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '7.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: activesupport
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '7.0'
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '7.0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: diff-lcs
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.5'
|
|
48
|
+
type: :runtime
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.5'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '3.12'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '3.12'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: sqlite3
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '1.4'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '1.4'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: factory_bot
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '6.0'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '6.0'
|
|
97
|
+
- !ruby/object:Gem::Dependency
|
|
98
|
+
name: rubocop
|
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
|
100
|
+
requirements:
|
|
101
|
+
- - "~>"
|
|
102
|
+
- !ruby/object:Gem::Version
|
|
103
|
+
version: '1.50'
|
|
104
|
+
type: :development
|
|
105
|
+
prerelease: false
|
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
+
requirements:
|
|
108
|
+
- - "~>"
|
|
109
|
+
- !ruby/object:Gem::Version
|
|
110
|
+
version: '1.50'
|
|
111
|
+
description: |
|
|
112
|
+
Docsmith adds snapshot-based versioning to any ActiveRecord model with zero system dependencies.
|
|
113
|
+
|
|
114
|
+
• Full content snapshots (HTML, Markdown, JSON) for instant rollbacks
|
|
115
|
+
• Format-aware diff engine: word-level diffs for Markdown, tag-atomic diffs for HTML
|
|
116
|
+
• Document-level and range-anchored comments with threading and version migration
|
|
117
|
+
• Per-class configuration, debounced auto-save, lifecycle events, and a clean API
|
|
118
|
+
|
|
119
|
+
Perfect for wikis, CMS pages, API specs, legal documents, or any content that needs
|
|
120
|
+
an audit trail and inline collaboration.
|
|
121
|
+
email:
|
|
122
|
+
- swastik.thapaliya@gmail.com
|
|
123
|
+
executables: []
|
|
124
|
+
extensions: []
|
|
125
|
+
extra_rdoc_files: []
|
|
126
|
+
files:
|
|
127
|
+
- ".rspec"
|
|
128
|
+
- ".rspec_status"
|
|
129
|
+
- CHANGELOG.md
|
|
130
|
+
- CODE_OF_CONDUCT.md
|
|
131
|
+
- LICENSE.txt
|
|
132
|
+
- README.md
|
|
133
|
+
- Rakefile
|
|
134
|
+
- USAGE.md
|
|
135
|
+
- docs/superpowers/plans/2026-04-01-docsmith-full-plan.md
|
|
136
|
+
- docs/superpowers/plans/2026-04-08-parsers-remove-branches-docs.md
|
|
137
|
+
- docs/superpowers/specs/2026-04-01-docsmith-phase1-design.md
|
|
138
|
+
- docsmith_spec.md
|
|
139
|
+
- lib/docsmith.rb
|
|
140
|
+
- lib/docsmith/auto_save.rb
|
|
141
|
+
- lib/docsmith/comments/anchor.rb
|
|
142
|
+
- lib/docsmith/comments/comment.rb
|
|
143
|
+
- lib/docsmith/comments/manager.rb
|
|
144
|
+
- lib/docsmith/comments/migrator.rb
|
|
145
|
+
- lib/docsmith/configuration.rb
|
|
146
|
+
- lib/docsmith/diff.rb
|
|
147
|
+
- lib/docsmith/diff/engine.rb
|
|
148
|
+
- lib/docsmith/diff/parsers/html.rb
|
|
149
|
+
- lib/docsmith/diff/parsers/markdown.rb
|
|
150
|
+
- lib/docsmith/diff/renderers.rb
|
|
151
|
+
- lib/docsmith/diff/renderers/base.rb
|
|
152
|
+
- lib/docsmith/diff/renderers/registry.rb
|
|
153
|
+
- lib/docsmith/diff/result.rb
|
|
154
|
+
- lib/docsmith/document.rb
|
|
155
|
+
- lib/docsmith/document_version.rb
|
|
156
|
+
- lib/docsmith/errors.rb
|
|
157
|
+
- lib/docsmith/events/event.rb
|
|
158
|
+
- lib/docsmith/events/hook_registry.rb
|
|
159
|
+
- lib/docsmith/events/notifier.rb
|
|
160
|
+
- lib/docsmith/rendering/html_renderer.rb
|
|
161
|
+
- lib/docsmith/rendering/json_renderer.rb
|
|
162
|
+
- lib/docsmith/version.rb
|
|
163
|
+
- lib/docsmith/version_manager.rb
|
|
164
|
+
- lib/docsmith/version_tag.rb
|
|
165
|
+
- lib/docsmith/versionable.rb
|
|
166
|
+
- lib/generators/docsmith/install/install_generator.rb
|
|
167
|
+
- lib/generators/docsmith/install/templates/create_docsmith_tables.rb.erb
|
|
168
|
+
- lib/generators/docsmith/install/templates/docsmith_initializer.rb.erb
|
|
169
|
+
- sig/docsmith.rbs
|
|
170
|
+
homepage: https://www.altcipher.com
|
|
171
|
+
licenses:
|
|
172
|
+
- MIT
|
|
173
|
+
metadata:
|
|
174
|
+
homepage_uri: https://www.altcipher.com
|
|
175
|
+
source_code_uri: https://github.com/swastik009/docsmith
|
|
176
|
+
changelog_uri: https://github.com/swastik009/docsmith/blob/main/CHANGELOG.md
|
|
177
|
+
post_install_message:
|
|
178
|
+
rdoc_options: []
|
|
179
|
+
require_paths:
|
|
180
|
+
- lib
|
|
181
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
182
|
+
requirements:
|
|
183
|
+
- - ">="
|
|
184
|
+
- !ruby/object:Gem::Version
|
|
185
|
+
version: 3.1.0
|
|
186
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
187
|
+
requirements:
|
|
188
|
+
- - ">="
|
|
189
|
+
- !ruby/object:Gem::Version
|
|
190
|
+
version: '0'
|
|
191
|
+
requirements: []
|
|
192
|
+
rubygems_version: 3.3.27
|
|
193
|
+
signing_key:
|
|
194
|
+
specification_version: 4
|
|
195
|
+
summary: Plug-and-play document version manager for Rails
|
|
196
|
+
test_files: []
|