im_reader 1.0.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16d4011ce1785af650ee6e0fee1f9086c3dc7e7f7699da7749dc486ead737bee
4
- data.tar.gz: 5e9d3fc80714da3b4fed4c9fb9b7bcfd12925c189acc5fcce13dce41bac8d22d
3
+ metadata.gz: 9df80b54a543f446c9a12e0f09d296813cb861cd44f1ed283d5b30e3df57797c
4
+ data.tar.gz: 48e211b702f2dd7e3261db75d8be8d48c7c759e0dd7c23b85fdaa63148f0ad08
5
5
  SHA512:
6
- metadata.gz: 7e812b464dd5a8a99cc8e039599bcedf75fe22e244f5a37225f947b8370f0d71c8b1253809948524e46aab7e3ac4dead16b01b57e5146f4edac3e130fed7100e
7
- data.tar.gz: 6a2992b497ef8950165ac03c8e1459a01f92ff2ab6b11b1089cb7361dbf1850f95a3a663dd7abc7495ab0cbabe2c2699a75ec108034d38f3785c46eb1822e5c9
6
+ metadata.gz: df2f8086643d74000d8fed67ad0f0068b56966757c4de95fe72ed38f4f8a113486294f0240a16c95d353b6cd3b0784b3313de5188e18a7ce4b10d02cd9959788
7
+ data.tar.gz: 066ed3cb0d3ead6971f51589aa722c872b77f9394d107246e89b1b9cb02d74aacbb5446d5247d4e53e3fafab02ce7103914db1ea8e8e3010b020a9dac875db64
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # im_reader
2
2
 
3
- **im_reader** is a mountable Ruby on Rails engine that provides an elegant in-browser EPUB reader.
4
- It’s designed to integrate seamlessly into your Rails application, allowing users to read `.epub` files hosted remotely, with a built-in table of contents, cover display, and customizable UI labels.
3
+ **im_reader** is a mountable Ruby on Rails engine that encapsulates the [`epub.js`](https://github.com/intity/epub-js) JavaScript library to provide an elegant in-browser EPUB reader.
4
+ It integrates [`Semantic UI`](https://github.com/Semantic-Org/Semantic-UI) for the user interface styling, offering a modern and responsive reading experience.
5
+ Designed to fit seamlessly into any Rails application, it allows users to read `.epub` files hosted remotely, with a built-in table of contents, cover display, and customizable UI labels.
5
6
 
6
7
  ---
7
8
 
@@ -48,7 +49,7 @@ This will expose the following route inside your application:
48
49
  ### Example usage
49
50
 
50
51
  ```
51
- /reader/remote?url=https://your-server.com/path/to/book.epub
52
+ /reader/epub?url=https://your-server.com/path/to/book.epub
52
53
  ```
53
54
 
54
55
  This endpoint will download the EPUB file from the given URL, stream it securely to the client, and display it in the embedded reader.
@@ -76,6 +77,7 @@ fr:
76
77
  start: "Commencer la lecture"
77
78
  messages:
78
79
  loading: "Chargement du livre ..."
80
+ reading_error: "Impossible de récupérer l’EPUB"
79
81
  ```
80
82
 
81
83
  ---
@@ -108,3 +110,12 @@ http://localhost:3000/reader/epub?url=https://your-server.com/path/to/book.epub
108
110
 
109
111
  This project is distributed under the MIT License.
110
112
  See the `LICENSE` file for more details.
113
+
114
+ ## Credits
115
+
116
+ - **EPUB rendering:** [epub.js (Intity fork)](https://github.com/intity/epub-js)
117
+ Licensed under the BSD 2-Clause License.
118
+ - **UI framework:** [Semantic UI](https://github.com/Semantic-Org/Semantic-UI)
119
+ Licensed under the MIT License.
120
+
121
+ **im_reader** integrates and extends these open-source projects to provide a smooth EPUB reading experience inside Ruby on Rails.
@@ -19,17 +19,43 @@
19
19
  $("#reader-root").append(overlay);
20
20
 
21
21
  var bookUrl = $root.data("book-url");
22
+ if (!bookUrl) {
23
+ $("#cover-content").html(`<p class="ui red text">${i18n.missing_url || "URL manquante."}</p>`);
24
+ return;
25
+ }
22
26
  var theme = "light";
23
27
  var fontSize = 100;
28
+ fetch(bookUrl, { method: "HEAD" })
29
+ .then((res) => {
30
+ if (!res.ok) {
31
+ // Si HEAD échoue, on tente GET pour récupérer le message texte
32
+ return fetch(bookUrl).then(async (resp) => {
33
+ const msg = await resp.text();
34
+ throw new Error(msg || i18n.reading_error || "Erreur de lecture.");
35
+ });
36
+ }
37
+ })
38
+ .then(() => startReader(bookUrl, i18n, overlay))
39
+ .catch((err) => {
40
+ console.error("[im_reader] load error:", err);
41
+ $("#cover-content").html(`<p class="ui red text">${err.message}</p>`);
42
+ });
43
+ }
24
44
 
45
+ function startReader(bookUrl, i18n, overlay) {
25
46
  var $viewer = $("#viewer"), $toc = $("#toc");
26
- var $prev_button = $("#prev_button"), $next_button = $("#next_button"), $btnTheme = $("#btnTheme");
47
+ var $prev_button = $("#prev_button"), $next_button = $("#next_button");
27
48
 
28
- if (!window.ePub) { console.error("ePub introuvable"); return; }
49
+ if (!window.ePub) {
50
+ $("#cover-content").html(`<p class="ui red text">ePub introuvable</p>`);
51
+ return;
52
+ }
29
53
 
30
- window.book = window.ePub(bookUrl, { openAs: "epub",
54
+ window.book = window.ePub(bookUrl, {
55
+ openAs: "epub",
31
56
  replacements: "blob",
32
- restore: false });
57
+ restore: false
58
+ });
33
59
 
34
60
  var rendition = book.renderTo("viewer", {
35
61
  width: "100%",
@@ -53,34 +79,17 @@
53
79
  if (iframe) iframe.setAttribute("sandbox", "allow-same-origin allow-scripts");
54
80
  });
55
81
 
56
- $prev_button.on("click", () => {
57
- rendition.prev();
58
- });
59
-
60
- $next_button.on("click", () => {
61
- rendition.next();
62
- });
63
-
64
- rendition.on("relocated", (location) => {
65
- if (book.locations.total > 0) {
66
- const current = book.locations.locationFromCfi(location.start.cfi);
67
- const total = book.locations.total;
68
- let percentage = current / total;
69
- if (location.atEnd) percentage = 1.0;
70
-
71
- $('#progressBar').progress('set percent', Math.round(percentage * 100));
72
- }
73
- });
82
+ $prev_button.on("click", () => rendition.prev());
83
+ $next_button.on("click", () => rendition.next());
74
84
 
75
85
  book.ready.then(() => Promise.all([book.loaded.navigation, book.loaded.manifest]))
76
86
  .then(([toc, manifest]) => {
77
-
78
87
  book.coverUrl().then((url) => {
79
88
  if (url) {
80
89
  $("#cover-content").html(`
81
- <img src="${url}" alt="${i18n.book_cover}">
82
- <button id="start_button" class="ui primary button">${i18n.start}</button>
83
- `);
90
+ <img src="${url}" alt="${i18n.book_cover}">
91
+ <button id="start_button" class="ui primary button">${i18n.start}</button>
92
+ `);
84
93
 
85
94
  overlay.on("click", "#start_button", () => {
86
95
  overlay.remove();
@@ -93,51 +102,24 @@
93
102
  });
94
103
 
95
104
  const $toc = $("#toc");
96
-
97
- function normalizeHref(href) {
98
- for (const key in manifest) {
99
- const entry = manifest[key];
100
- if (
101
- entry.href.endsWith(href) ||
102
- entry.href.endsWith("x" + href) ||
103
- entry.href.endsWith("/" + href)
104
- ) {
105
- return entry.href;
106
- }
107
- }
108
-
109
- // Par défaut
110
- return href;
111
- }
112
-
113
105
  toc.toc.forEach((chapter) => {
114
- const resolvedHref = normalizeHref(chapter.href);
115
-
116
106
  $("<a>")
117
107
  .addClass("item")
118
108
  .attr("href", "#")
119
109
  .text(chapter.label)
120
110
  .on("click", (e) => {
121
111
  e.preventDefault();
122
- rendition
123
- .display(resolvedHref)
124
- .catch((err) => {
125
- console.error("[im_reader] Display error:", err.message, resolvedHref);
126
- });
112
+ rendition.display(chapter.href).catch((err) => {
113
+ console.error("[im_reader] Display error:", err.message);
114
+ });
127
115
  })
128
116
  .appendTo($toc);
129
117
  });
130
118
  })
131
- .catch((err) => console.error("[im_reader] TOC load error:", err));
132
-
133
- book.ready.then(() => {
134
- return book.locations.generate(1000);
135
- }).then(() => {
136
- }).catch(e => console.error("Error, book not ready", e));
137
-
138
- rendition.on("displayError", (e)=> console.error("Error on display", e));
139
-
140
- rendition.display().catch(e=>console.error("Error while rendering book", e));
119
+ .catch((err) => {
120
+ console.error("[im_reader] TOC load error:", err);
121
+ $("#cover-content").html(`<p class="ui red text">${i18n.reading_error || "Erreur de lecture du livre."}</p>`);
122
+ });
141
123
  }
142
124
 
143
125
  $(function(){ var $root = $("#reader-root"); if ($root.length) initReader($root); });
@@ -4,37 +4,47 @@ module ImReader
4
4
 
5
5
  def show
6
6
  @remote_url = params[:url]
7
- puts "#### PLOP ####"
8
7
  @url = im_reader.remote_epub_reader_url(url: @remote_url)
9
- puts "***** OK *****"
10
8
  end
11
9
 
12
10
  def remote
13
- # Envoyer cette url depuis le BO immateriel
14
- # @book.book_previews&.last&.download_url
15
- puts "***** REMOTE 1 *****"
16
- url = params[:url]
17
- uri = URI.parse(url)
18
- response = fetch_with_redirect(uri)
11
+ raw_url = params[:url].to_s.strip
12
+ return render plain: I18n.t('im_reader.messages.missing_url'), status: 400 if raw_url.empty?
13
+ uri = parse_uri(raw_url)
14
+ return render plain: I18n.t('im_reader.messages.invalid_url'), status: 400 unless uri
19
15
 
20
- # on télécharge directement en mémoire
21
- puts "***** REMOTE 2 *****"
16
+ response = fetch_with_redirect(uri)
22
17
 
23
18
  if response.is_a?(Net::HTTPSuccess)
24
-
25
- puts "***** REMOTE 3.1 *****"
26
19
  send_data response.body,
27
20
  filename: File.basename(uri.path.presence || "remote.epub"),
28
21
  type: "application/epub+zip",
29
22
  disposition: "inline"
30
23
  else
31
- puts "***** REMOTE 3.2 *****"
32
- render plain: "Impossible de récupérer l’EPUB", status: 404
24
+ render plain: I18n.t('im_reader.messages.reading_error'), status: 404
33
25
  end
34
26
  end
35
27
 
36
28
  private
37
29
 
30
+ def parse_uri(value)
31
+ raw = value.to_s.strip
32
+ return nil if raw.empty?
33
+
34
+ candidates = [raw, CGI.unescape(raw)].uniq
35
+
36
+ candidates.each do |c|
37
+ begin
38
+ uri = URI.parse(c)
39
+ return uri if uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
40
+ rescue URI::InvalidURIError, URI::InvalidComponentError
41
+ next
42
+ end
43
+ end
44
+
45
+ nil
46
+ end
47
+
38
48
  def fetch_with_redirect(uri, limit = 5)
39
49
  raise "Too many redirects" if limit == 0
40
50
 
@@ -3,6 +3,7 @@
3
3
  data-i18n='<%= {
4
4
  start: t("im_reader.buttons.start"),
5
5
  loading: t("im_reader.messages.loading"),
6
+ missing_url: t("im_reader.messages.missing_url"),
6
7
  book_cover: t("im_reader.elements.book_cover")
7
8
  }.to_json %>'>
8
9
  <aside class="ui vertical menu">
@@ -7,4 +7,7 @@ de:
7
7
  buttons:
8
8
  start: "Lesen beginnen"
9
9
  messages:
10
+ invalid_url: "Die URL zum Lesen ist ungültig."
11
+ missing_url: "Die Lese-URL fehlt."
10
12
  loading: "Buch wird geladen..."
13
+ reading_error: "EPUB-Datei konnte nicht abgerufen werden"
@@ -7,4 +7,7 @@ en:
7
7
  buttons:
8
8
  start: "Start to Read"
9
9
  messages:
10
+ invalid_url: "The URL provide is invalid."
11
+ missing_url: "The reading URL is missing."
10
12
  loading: "Book is loading ..."
13
+ reading_error: "Unable to retrieve the EPUB"
@@ -7,4 +7,7 @@ es:
7
7
  buttons:
8
8
  start: "Comenzar la lectura"
9
9
  messages:
10
+ invalid_url: "La URL para leer no es válida."
11
+ missing_url: "Falta la URL de lectura."
10
12
  loading: "Cargando el libro..."
13
+ reading_error: "No se pudo recuperar el EPUB"
@@ -7,4 +7,7 @@ fr:
7
7
  buttons:
8
8
  start: "Commencer la lecture"
9
9
  messages:
10
+ invalid_url: "L'url de lecture est invalide."
11
+ missing_url: "L'url de lecture est manquante."
10
12
  loading: "Chargement du livre ..."
13
+ reading_error: "Impossible de récupérer l’EPUB"
@@ -7,4 +7,7 @@ pt:
7
7
  buttons:
8
8
  start: "Começar a leitura"
9
9
  messages:
10
+ invalid_url: "O URL para leitura é inválido."
11
+ missing_url: "O URL de leitura está em falta."
10
12
  loading: "Carregando o livro..."
13
+ reading_error: "Não foi possível recuperar o EPUB."
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ImReader
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: im_reader
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elodie Ailleaume
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-20 00:00:00.000000000 Z
11
+ date: 2025-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sass