diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-07-10 00:35:24 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-07-10 00:35:24 +0200 |
| commit | a25d90335ad3738b6831288190132c2f7498465c (patch) | |
| tree | 5aa7083d9f8c8701644fe60bc7d28542775694b8 /app | |
| parent | da0b79a7c1f3116e2b450ecbe50efdf31cf23414 (diff) | |
Retire vendored cacycle_diff.js for a diff-lcs-based diff view
Revisions#diff now computes an inline or side-by-side word diff
server-side (Page#diff_against, lib/html_word_diff.rb) instead of
shipping raw/escaped content to the browser for a 2008-era vendored
JS differ to mangle. View mode is picked via a `view` param, settable
either on the diff page itself or directly from the revisions#index
sticky bar next to "Diff revisions".
Also fixes a pre-existing bug in RevisionsController#diff's single-
revision branch, which set params[:start]/params[:end] instead of
params[:start_revision]/params[:end_revision].
Diffstat (limited to 'app')
| -rw-r--r-- | app/controllers/revisions_controller.rb | 12 | ||||
| -rw-r--r-- | app/models/page.rb | 16 | ||||
| -rw-r--r-- | app/views/revisions/diff.html.erb | 78 | ||||
| -rw-r--r-- | app/views/revisions/index.html.erb | 8 |
4 files changed, 59 insertions, 55 deletions
diff --git a/app/controllers/revisions_controller.rb b/app/controllers/revisions_controller.rb index 42d667e..9acb26f 100644 --- a/app/controllers/revisions_controller.rb +++ b/app/controllers/revisions_controller.rb | |||
| @@ -13,16 +13,18 @@ class RevisionsController < ApplicationController | |||
| 13 | 13 | ||
| 14 | def diff | 14 | def diff |
| 15 | @node = Node.find(params[:node_id]) | 15 | @node = Node.find(params[:node_id]) |
| 16 | 16 | ||
| 17 | if @node.pages.length > 1 | 17 | if @node.pages.length > 1 |
| 18 | params[:start_revision] ||= @node.pages.all[-2].revision | 18 | params[:start_revision] ||= @node.pages.all[-2].revision |
| 19 | params[:end_revision] ||= @node.pages.all[-1].revision | 19 | params[:end_revision] ||= @node.pages.all[-1].revision |
| 20 | else | 20 | else |
| 21 | params[:start], params[:end] = 1, 1 | 21 | params[:start_revision], params[:end_revision] = 1, 1 |
| 22 | end | 22 | end |
| 23 | 23 | ||
| 24 | @start = @node.pages.find_by_revision( params[:start_revision] ) | 24 | @start = @node.pages.find_by_revision( params[:start_revision] ) |
| 25 | @end = @node.pages.find_by_revision( params[:end_revision] ) | 25 | @end = @node.pages.find_by_revision( params[:end_revision] ) |
| 26 | @diff_view = params[:view] == "side_by_side" ? :side_by_side : :inline | ||
| 27 | @diff = @end.diff_against( @start, view: @diff_view ) | ||
| 26 | end | 28 | end |
| 27 | 29 | ||
| 28 | def show | 30 | def show |
diff --git a/app/models/page.rb b/app/models/page.rb index 1a98e08..ea04cd6 100644 --- a/app/models/page.rb +++ b/app/models/page.rb | |||
| @@ -175,6 +175,22 @@ class Page < ApplicationRecord | |||
| 175 | self.save | 175 | self.save |
| 176 | end | 176 | end |
| 177 | 177 | ||
| 178 | def diff_against other, view: :inline | ||
| 179 | if view == :side_by_side | ||
| 180 | { | ||
| 181 | title: HtmlWordDiff.side_by_side(other.title.to_s, title.to_s), | ||
| 182 | abstract: HtmlWordDiff.side_by_side(other.abstract.to_s, abstract.to_s), | ||
| 183 | body: HtmlWordDiff.side_by_side(other.body.to_s, body.to_s) | ||
| 184 | } | ||
| 185 | else | ||
| 186 | { | ||
| 187 | title: HtmlWordDiff.inline(other.title.to_s, title.to_s), | ||
| 188 | abstract: HtmlWordDiff.inline(other.abstract.to_s, abstract.to_s), | ||
| 189 | body: HtmlWordDiff.inline(other.body.to_s, body.to_s) | ||
| 190 | } | ||
| 191 | end | ||
| 192 | end | ||
| 193 | |||
| 178 | def public? | 194 | def public? |
| 179 | published_at.nil? ? true : published_at < Time.now | 195 | published_at.nil? ? true : published_at < Time.now |
| 180 | end | 196 | end |
diff --git a/app/views/revisions/diff.html.erb b/app/views/revisions/diff.html.erb index d8c6a47..d7bb528 100644 --- a/app/views/revisions/diff.html.erb +++ b/app/views/revisions/diff.html.erb | |||
| @@ -7,58 +7,36 @@ | |||
| 7 | <%= form_tag diff_node_revisions_path do %> | 7 | <%= form_tag diff_node_revisions_path do %> |
| 8 | <%= select_tag :start_revision, options_for_select(@node.pages.map{|x| x.revision}, params[:start_revision].to_i) %> | 8 | <%= select_tag :start_revision, options_for_select(@node.pages.map{|x| x.revision}, params[:start_revision].to_i) %> |
| 9 | <%= select_tag :end_revision, options_for_select(@node.pages.map{|x| x.revision}, params[:end_revision].to_i) %> | 9 | <%= select_tag :end_revision, options_for_select(@node.pages.map{|x| x.revision}, params[:end_revision].to_i) %> |
| 10 | <%= select_tag :view, options_for_select([['Inline', 'inline'], ['Side by side', 'side_by_side']], @diff_view) %> | ||
| 10 | <%= submit_tag 'Diff' %> | 11 | <%= submit_tag 'Diff' %> |
| 11 | <% end %> | 12 | <% end %> |
| 12 | 13 | ||
| 13 | <div id="start_title" style="display: none;"> | ||
| 14 | <%= h(@start.title) %> | ||
| 15 | </div> | ||
| 16 | |||
| 17 | <div id="end_title" style="display: none;"> | ||
| 18 | <%= h(@end.title) %> | ||
| 19 | </div> | ||
| 20 | |||
| 21 | <div id="start_abstract" style="display: none;"> | ||
| 22 | <%= h(@start.abstract) %> | ||
| 23 | </div> | ||
| 24 | |||
| 25 | <div id="end_abstract" style="display: none;"> | ||
| 26 | <%= h(@end.abstract) %> | ||
| 27 | </div> | ||
| 28 | |||
| 29 | <pre> | ||
| 30 | <div id="start_body" style="display: none;"> | ||
| 31 | <%= (@start.body) %> | ||
| 32 | </div> | ||
| 33 | </pre> | ||
| 34 | <pre> | ||
| 35 | <div id="end_body" style="display: none;"> | ||
| 36 | <%= (@end.body) %> | ||
| 37 | </div> | ||
| 38 | </pre> | ||
| 39 | |||
| 40 | <script src="/javascripts/cacycle_diff.js"></script> | ||
| 41 | <script type="text/javascript" charset="utf-8"> | ||
| 42 | window.onload = function() { | ||
| 43 | title1 = document.getElementById('start_title').innerHTML; | ||
| 44 | title2 = document.getElementById('end_title').innerHTML; | ||
| 45 | abstract1 = document.getElementById('start_abstract').innerHTML; | ||
| 46 | abstract2 = document.getElementById('end_abstract').innerHTML; | ||
| 47 | body1 = document.getElementById('start_body').innerHTML; | ||
| 48 | body2 = document.getElementById('end_body').innerHTML; | ||
| 49 | document.getElementById('diffview_title').innerHTML = WDiffString(title1, title2); | ||
| 50 | document.getElementById('diffview_abstract').innerHTML = WDiffString(abstract1, abstract2); | ||
| 51 | document.getElementById('diffview_body').innerHTML = WDiffString(body1, body2); | ||
| 52 | } | ||
| 53 | </script> | ||
| 54 | |||
| 55 | <div id="diffview"> | 14 | <div id="diffview"> |
| 56 | <h3>Title</h3> | 15 | <% if @diff_view == :side_by_side %> |
| 57 | <p id="diffview_title"></p> | 16 | <div class="diff_side_by_side"> |
| 58 | 17 | <div class="diff_column"> | |
| 59 | <h3>Abstract</h3> | 18 | <h3>Title</h3> |
| 60 | <p id="diffview_abstract"></p> | 19 | <p><%= raw @diff[:title][0] %></p> |
| 61 | 20 | <h3>Abstract</h3> | |
| 62 | <h3>Body</h3> | 21 | <p><%= raw @diff[:abstract][0] %></p> |
| 63 | <p id="diffview_body"></p> | 22 | <h3>Body</h3> |
| 23 | <%= raw @diff[:body][0] %> | ||
| 24 | </div> | ||
| 25 | <div class="diff_column"> | ||
| 26 | <h3>Title</h3> | ||
| 27 | <p><%= raw @diff[:title][1] %></p> | ||
| 28 | <h3>Abstract</h3> | ||
| 29 | <p><%= raw @diff[:abstract][1] %></p> | ||
| 30 | <h3>Body</h3> | ||
| 31 | <%= raw @diff[:body][1] %> | ||
| 32 | </div> | ||
| 33 | </div> | ||
| 34 | <% else %> | ||
| 35 | <h3>Title</h3> | ||
| 36 | <p><%= raw @diff[:title] %></p> | ||
| 37 | <h3>Abstract</h3> | ||
| 38 | <p><%= raw @diff[:abstract] %></p> | ||
| 39 | <h3>Body</h3> | ||
| 40 | <%= raw @diff[:body] %> | ||
| 41 | <% end %> | ||
| 64 | </div> | 42 | </div> |
diff --git a/app/views/revisions/index.html.erb b/app/views/revisions/index.html.erb index a6a981a..58c08b7 100644 --- a/app/views/revisions/index.html.erb +++ b/app/views/revisions/index.html.erb | |||
| @@ -19,6 +19,8 @@ | |||
| 19 | form: { id: 'diff_form', class: 'button_to computation' }, | 19 | form: { id: 'diff_form', class: 'button_to computation' }, |
| 20 | disabled: true %> | 20 | disabled: true %> |
| 21 | <span id="diff_selection_label"></span> | 21 | <span id="diff_selection_label"></span> |
| 22 | <label><%= radio_button_tag :view, 'inline', true %> Inline</label> | ||
| 23 | <label><%= radio_button_tag :view, 'side_by_side', false %> Side by side</label> | ||
| 22 | </td> | 24 | </td> |
| 23 | </tr> | 25 | </tr> |
| 24 | </thead> | 26 | </thead> |
| @@ -68,6 +70,7 @@ | |||
| 68 | document.getElementById('diff_form').addEventListener('submit', function(e) { | 70 | document.getElementById('diff_form').addEventListener('submit', function(e) { |
| 69 | var start = document.querySelector('input[name="start_revision"]:checked'); | 71 | var start = document.querySelector('input[name="start_revision"]:checked'); |
| 70 | var end = document.querySelector('input[name="end_revision"]:checked'); | 72 | var end = document.querySelector('input[name="end_revision"]:checked'); |
| 73 | var view = document.querySelector('input[name="view"]:checked'); | ||
| 71 | if (start) { | 74 | if (start) { |
| 72 | var s = document.createElement('input'); | 75 | var s = document.createElement('input'); |
| 73 | s.type = 'hidden'; s.name = 'start_revision'; s.value = start.value; | 76 | s.type = 'hidden'; s.name = 'start_revision'; s.value = start.value; |
| @@ -78,5 +81,10 @@ | |||
| 78 | en.type = 'hidden'; en.name = 'end_revision'; en.value = end.value; | 81 | en.type = 'hidden'; en.name = 'end_revision'; en.value = end.value; |
| 79 | this.appendChild(en); | 82 | this.appendChild(en); |
| 80 | } | 83 | } |
| 84 | if (view) { | ||
| 85 | var v = document.createElement('input'); | ||
| 86 | v.type = 'hidden'; v.name = 'view'; v.value = view.value; | ||
| 87 | this.appendChild(v); | ||
| 88 | } | ||
| 81 | }); | 89 | }); |
| 82 | </script> | 90 | </script> |
