fragment_highlighter-rails 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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c6408468fafdf63427c816205dd38a1215f48f4e
4
+ data.tar.gz: b461fe69e5f315729de0fcedde65a5a36fd38eb3
5
+ SHA512:
6
+ metadata.gz: b75f5574ec1c03e52cb652eee80b8d1e6d5274139d9f18d1dbb5d573945d421e6be293365729c71dcf1b4b19dfef92c12bfaea5a8fea7c861de231dca35ff9a1
7
+ data.tar.gz: 74e581c62c117065ad72acdfc4a4d7c34fd2119fa81d0a561b6ea53f202fefd07fb34d9ebd7377379364f8f910ca322b43e71c1f3ac63c7a8a47d247a49c74ca
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/tmp
10
+ /log
11
+ /tmp/
12
+
13
+ # Ignore Byebug command history file.
14
+ .byebug_history
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in fragment_highlighter-rails.gemspec
6
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 Flower Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,41 @@
1
+ # fragment_highlighter-rails
2
+ JavaScript UI library for highlighting text fragments on page and saving ones into localStorage
3
+
4
+ ## Installation
5
+
6
+ Add this line to your application's Gemfile:
7
+
8
+ ```ruby
9
+ gem 'fragment_highlighter-rails'
10
+ ```
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install fragment_highlighter-rails
19
+
20
+ ## Usage
21
+
22
+ #application.css
23
+
24
+ @import "bootstrap-sprockets";
25
+ @import "bootstrap";
26
+ *= require 'fragment-highlighter'
27
+
28
+ #application.js
29
+
30
+ //= require jquery
31
+ //= require bootstrap-sprockets
32
+ //= require fragment-highlighter/fragment-highlighter.js
33
+
34
+ $(function() {
35
+ let allowedPages = ['/articles/'];
36
+ new FragmentHighlighter(allowedPages);
37
+ });
38
+
39
+ ## Contributing
40
+
41
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/fragment_highlighter-rails.
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "fragment_highlighter/rails"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "fragment_highlighter/rails/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "fragment_highlighter-rails"
8
+ spec.version = FragmentHighlighter::Rails::VERSION
9
+ spec.authors = ["PhlowerTeam"]
10
+ spec.email = ["phlowerteam@gmail.com"]
11
+
12
+ spec.summary = %q{JavaScript UI library for highlighting text fragments on the page and saving ones into localStorage}
13
+ spec.homepage = "https://github.com/phlowerteam/fragment_highlighter-rails"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
16
+ f.match(%r{^(test|spec|features)/})
17
+ end
18
+ spec.bindir = "exe"
19
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_development_dependency "bundler", "~> 1.15"
23
+ spec.add_development_dependency "rake", "~> 10.0"
24
+ spec.add_development_dependency "bootstrap-sass", "~> 3.3.5"
25
+ spec.add_development_dependency "jquery-rails"
26
+ end
@@ -0,0 +1,8 @@
1
+ require "fragment_highlighter/rails/version"
2
+
3
+ module FragmentHighlighter
4
+ module Rails
5
+ class Engine < ::Rails::Engine
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ module FragmentHighlighter
2
+ module Rails
3
+ VERSION = "0.1.0"
4
+ end
5
+ end
@@ -0,0 +1,35 @@
1
+ //= require fragment-highlighter/libs/jquery.mark.es6.min.js
2
+ //= require fragment-highlighter/libs/classes/HView.js
3
+ //= require fragment-highlighter/libs/classes/HWindowCtrls.js
4
+ //= require fragment-highlighter/libs/classes/LS.js
5
+ //= require fragment-highlighter/libs/classes/Marker.js
6
+ //= require fragment-highlighter/libs/classes/Tools.js
7
+ //= require fragment-highlighter/libs/classes/UserSettings.js
8
+
9
+ class FragmentHighlighter {
10
+
11
+ constructor(pages) {
12
+ FragmentHighlighter.init(pages);
13
+ }
14
+
15
+ static init(allowedPages) {
16
+ $(function(){
17
+ if (HView.isSupportedPage(allowedPages)) {
18
+ HView.renderModeButton();
19
+ }
20
+
21
+ HView.initHighlightWindow();
22
+ UserSettings.init();
23
+
24
+ if (HView.isHModeOn(allowedPages)) {
25
+ $(document.body).on('click', '.remove-text', function(e){
26
+ Marker.removeMarked(e.target);
27
+ });
28
+
29
+ $(document.body).bind('mouseup', function(e){
30
+ Marker.extractFragment();
31
+ });
32
+ }
33
+ });
34
+ }
35
+ }
@@ -0,0 +1,68 @@
1
+ class HView {
2
+
3
+ static isHModeOn(allowedPages) {
4
+ return (UserSettings.isHMode() && HView.isSupportedPage(allowedPages));
5
+ }
6
+
7
+ static isSupportedPage(allowedPages) {
8
+ let matcher = new RegExp("(" + allowedPages.join('|') + ")", "g");
9
+ return matcher.test(window.location.href)
10
+ }
11
+
12
+ static renderModeButton() {
13
+ // https://www.w3schools.com/howto/howto_css_sidenav_buttons.asp
14
+ $('body').prepend(`
15
+ <div id='highlightSidenav' class='sidenav'>
16
+ <a href='#' id='h-mode' onclick='HWindowCtrls.showHighlightWindow()'>
17
+ <span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
18
+ </a>
19
+ </div>`);
20
+ }
21
+
22
+ static initHighlightWindow() {
23
+ $('body').prepend(`
24
+ <div class="modal fade" id="highlightModal" tabindex="-1" role="dialog" aria-labelledby="highlightModalLabel" aria-hidden="true">
25
+ <div class="modal-dialog">
26
+ <div class="modal-content">
27
+ <div class="modal-header">
28
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
29
+ <h4 class="modal-title" id="myModalLabel">Highlighted Articles</h4>
30
+ </div>
31
+ <div class="modal-body" id="marked-items-list">
32
+ ...
33
+ </div>
34
+ <div class="modal-footer">
35
+ <row>
36
+ <div class="col-md-8">
37
+ <div class="checkbox">
38
+ <label><input type="checkbox" id="hmode_checkbox" value="" onclick="HWindowCtrls.turnOnHMode()"> TURN ON Highlighting Mode</label>
39
+ </div>
40
+ </div>
41
+ <div class="col-md-4">
42
+ <button type="button" class="btn btn-primary" onclick='HWindowCtrls.exportJSON()'>Export Settings</button>
43
+ </div>
44
+ </row>
45
+ <br>
46
+ <br>
47
+ <row>
48
+ <div class="col-md-12">
49
+ <form id="jsonFile" name="jsonFile" enctype="multipart/form-data" method="post">
50
+ <fieldset>
51
+ <label class="custom-file">
52
+ <input type="file" id="fileinput" class="custom-file-input" required>
53
+ <span class="custom-file-control"></span>
54
+ </label>
55
+ <input type='button' class="btn btn-success" id='btnLoad' value='Import Settings' onclick='HWindowCtrls.importJSON();'>
56
+ </fieldset>
57
+ </form>
58
+ </div>
59
+ </row>
60
+ </div>
61
+ <a id="downloadAnchorElem" style="display:none"></a>
62
+ </div>
63
+ </div>
64
+ </div>`);
65
+
66
+ $('#highlightModal').modal({ show: false });
67
+ }
68
+ }
@@ -0,0 +1,64 @@
1
+ class HWindowCtrls {
2
+
3
+ static showHighlightWindow(){
4
+ $('#marked-items-list').html('');
5
+ let userSettings = LS.uSettings();
6
+ for (let key in userSettings.articles_titles) {
7
+ $('#marked-items-list').append(`<span><a href='${key}' target='_blank'>${userSettings.articles_titles[key]}</a>&nbsp<a href="#" onclick="HWindowCtrls.deleteFragments('${key}')">[Delete]</a></span><br>`);
8
+ };
9
+ $('input#hmode_checkbox')[0].checked = userSettings.settings.hmode_is_on;
10
+ $('#highlightModal').modal();
11
+ }
12
+
13
+ static exportJSON(){
14
+ let userSettings = LS.uSettings();
15
+ let dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(userSettings));
16
+ let dlAnchorElem = document.getElementById('downloadAnchorElem');
17
+ dlAnchorElem.setAttribute("href", dataStr );
18
+ dlAnchorElem.setAttribute("download", "scene.json");
19
+ dlAnchorElem.click();
20
+ }
21
+
22
+ static importJSON(){
23
+ let input, file, fr;
24
+
25
+ if (typeof window.FileReader !== 'function') {
26
+ alert("The file API isn't supported on this browser yet.");
27
+ return;
28
+ }
29
+
30
+ input = document.getElementById('fileinput');
31
+ if (!input) {
32
+ alert("Um, couldn't find the fileinput element.");
33
+ }
34
+ else if (!input.files) {
35
+ alert("This browser doesn't seem to support the `files` property of file inputs.");
36
+ }
37
+ else if (!input.files[0]) {
38
+ alert("Please select a file before clicking 'Load'");
39
+ }
40
+ else {
41
+ file = input.files[0];
42
+ fr = new FileReader();
43
+ fr.onload = receivedText;
44
+ fr.readAsText(file);
45
+ }
46
+
47
+ function receivedText(e) {
48
+ LS.saveUSettings(JSON.parse(e.target.result));
49
+ Tools.reloadPage();
50
+ }
51
+ }
52
+
53
+ static turnOnHMode(){
54
+ let userSettings = LS.uSettings();
55
+ userSettings.settings.hmode_is_on = $('input#hmode_checkbox')[0].checked;
56
+ LS.saveUSettings(userSettings);
57
+ Tools.reloadPage();
58
+ }
59
+
60
+ static deleteFragments(url) {
61
+ UserSettings.deleteFragments(url);
62
+ Tools.reloadPage();
63
+ }
64
+ }
@@ -0,0 +1,10 @@
1
+ class LS { // LocalStorage
2
+
3
+ static uSettings() {
4
+ return JSON.parse(localStorage.getItem('userSettings'));
5
+ }
6
+
7
+ static saveUSettings(userSettings) {
8
+ localStorage.setItem('userSettings', JSON.stringify(userSettings));
9
+ }
10
+ }
@@ -0,0 +1,56 @@
1
+ class Marker {
2
+
3
+ static removeMarked(element){
4
+ let textElement = $(element).prev();
5
+ UserSettings.removeFragment($(textElement).text());
6
+ $(textElement).removeClass('highlight-marked');
7
+ $(textElement).unmark();
8
+ $(element).remove();
9
+ }
10
+
11
+ static extractFragment(element){
12
+ let selection;
13
+
14
+ if (window.getSelection) {
15
+ selection = window.getSelection();
16
+ } else if (document.selection) {
17
+ selection = document.selection.createRange();
18
+ }
19
+
20
+ if (selection.toString() !== '') {
21
+ Marker.saveFragments(selection.toString());
22
+ }
23
+ }
24
+
25
+ static saveFragments(text){
26
+ let fragments = text.trim().split("\n\n");
27
+ fragments.forEach( fragment => {
28
+ if (fragment.length > 0) {
29
+ UserSettings.saveFragment(fragment);
30
+ Marker.highlight(fragment.trim());
31
+ }
32
+ });
33
+ }
34
+
35
+ static highlight(text){
36
+ $("body").mark(text, {
37
+ "className": "highlight-marked",
38
+ "caseSensitive": true,
39
+ "separateWordSearch": false,
40
+ "diacritics": false
41
+ });
42
+
43
+ let highlights = $(".highlight-marked");
44
+ if (highlights){
45
+ highlights.each((i, hl) => {
46
+ let next = $(hl).next();
47
+ if(next && $(next).attr('class') == 'remove-text'){
48
+ } else {
49
+ $('<span class="remove-text">[X]</span>').insertAfter(hl);
50
+ }
51
+ })
52
+ }
53
+ $(".highlight-marked").css('background-color', 'yellow');
54
+ $(".remove-text").css('background-color', 'gold');
55
+ }
56
+ }
@@ -0,0 +1,9 @@
1
+ class Tools {
2
+ static href() {
3
+ return window.location.href.split('#')[0];
4
+ }
5
+
6
+ static reloadPage() {
7
+ location.reload();
8
+ }
9
+ }
@@ -0,0 +1,86 @@
1
+ // userSettings format:
2
+ // {
3
+ // articles: {
4
+ // 'url': [
5
+ // {
6
+ // text: 'fragment'
7
+ // }
8
+ // ]
9
+ // },
10
+ // articles_titles: {
11
+ // 'url': 'title'
12
+ // },
13
+ // settings: {
14
+ // hmode_is_on: true
15
+ // }
16
+ // }
17
+
18
+ class UserSettings {
19
+
20
+ static init() {
21
+ let userSettings = LS.uSettings();
22
+ if (!userSettings){
23
+ // if (true){ //for debug
24
+ let userSettings = {
25
+ articles: {},
26
+ articles_titles: {},
27
+ settings:{
28
+ hmode_is_on: true
29
+ }
30
+ };
31
+ LS.saveUSettings(userSettings);
32
+ } else {
33
+ let hModeOn = LS.uSettings().settings.hmode_is_on;
34
+ if(hModeOn){
35
+ let articleFragments = userSettings.articles[Tools.href()] || [];
36
+ articleFragments.forEach(fragment => {
37
+ Marker.highlight(fragment.text);
38
+ });
39
+ }
40
+ };
41
+ }
42
+
43
+ static isHMode() {
44
+ let mode;
45
+ try { mode = LS.uSettings().settings.hmode_is_on } catch(e) {};
46
+ return mode;
47
+ }
48
+
49
+ static isNotPresent(text){
50
+ let article = LS.uSettings().articles[Tools.href()];
51
+ return (article && article.findIndex(i => i.text.trim() === text.trim()) < 0);
52
+ }
53
+
54
+ static removeFragment(text){
55
+ let userSettings = LS.uSettings();
56
+ let articleFragments = userSettings.articles[Tools.href()] || [];
57
+ userSettings.articles[Tools.href()].forEach(((fragment, i) => {
58
+ if (fragment.text.trim() == text.trim()){
59
+ userSettings.articles[Tools.href()].splice(i, 1);
60
+ LS.saveUSettings(userSettings);
61
+ return;
62
+ }
63
+ }));
64
+ }
65
+
66
+ static saveFragment(text){
67
+ let userSettings = LS.uSettings();
68
+ userSettings.articles[Tools.href()] = userSettings.articles[Tools.href()] || [];
69
+ userSettings.articles_titles = userSettings.articles_titles || {};
70
+
71
+ if (UserSettings.isNotPresent(text)) {
72
+ userSettings.articles[Tools.href()].push({
73
+ text: text.trim()
74
+ });
75
+ }
76
+ userSettings.articles_titles[Tools.href()] = $('h1').text();
77
+ LS.saveUSettings(userSettings);
78
+ }
79
+
80
+ static deleteFragments(url) {
81
+ let userSettings = LS.uSettings();
82
+ delete userSettings.articles[url];
83
+ delete userSettings.articles_titles[url];
84
+ LS.saveUSettings(userSettings);
85
+ }
86
+ }
@@ -0,0 +1,7 @@
1
+ /*!***************************************************
2
+ * mark.js v8.11.0
3
+ * https://github.com/julmot/mark.js
4
+ * Copyright (c) 2014–2017, Julian Motz
5
+ * Released under the MIT license https://git.io/vwTVl
6
+ *****************************************************/
7
+ "use strict";((factory,window,document)=>{if(typeof define==="function"&&define.amd){define(["jquery"],jQuery=>{return factory(window,document,jQuery);});}else if(typeof module==="object"&&module.exports){module.exports=factory(window,document,require("jquery"));}else{factory(window,document,jQuery);}})((window,document,$)=>{class Mark{constructor(ctx){this.ctx=ctx;this.ie=false;const ua=window.navigator.userAgent;if(ua.indexOf("MSIE")>-1||ua.indexOf("Trident")>-1){this.ie=true;}}set opt(val){this._opt=Object.assign({},{"element":"","className":"","exclude":[],"iframes":false,"iframesTimeout":5000,"separateWordSearch":true,"diacritics":true,"synonyms":{},"accuracy":"partially","acrossElements":false,"caseSensitive":false,"ignoreJoiners":false,"ignoreGroups":0,"ignorePunctuation":[],"wildcards":"disabled","each":()=>{},"noMatch":()=>{},"filter":()=>true,"done":()=>{},"debug":false,"log":window.console},val);}get opt(){return this._opt;}get iterator(){return new DOMIterator(this.ctx,this.opt.iframes,this.opt.exclude,this.opt.iframesTimeout);}log(msg,level="debug"){const log=this.opt.log;if(!this.opt.debug){return;}if(typeof log==="object"&&typeof log[level]==="function"){log[level](`mark.js: ${msg}`);}}escapeStr(str){return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g,"\\$&");}createRegExp(str){if(this.opt.wildcards!=="disabled"){str=this.setupWildcardsRegExp(str);}str=this.escapeStr(str);if(Object.keys(this.opt.synonyms).length){str=this.createSynonymsRegExp(str);}if(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length){str=this.setupIgnoreJoinersRegExp(str);}if(this.opt.diacritics){str=this.createDiacriticsRegExp(str);}str=this.createMergedBlanksRegExp(str);if(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length){str=this.createJoinersRegExp(str);}if(this.opt.wildcards!=="disabled"){str=this.createWildcardsRegExp(str);}str=this.createAccuracyRegExp(str);return str;}createSynonymsRegExp(str){const syn=this.opt.synonyms,sens=this.opt.caseSensitive?"":"i",joinerPlaceholder=this.opt.ignoreJoiners||this.opt.ignorePunctuation.length?"\u0000":"";for(let index in syn){if(syn.hasOwnProperty(index)){const value=syn[index],k1=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(index):this.escapeStr(index),k2=this.opt.wildcards!=="disabled"?this.setupWildcardsRegExp(value):this.escapeStr(value);if(k1!==""&&k2!==""){str=str.replace(new RegExp(`(${k1}|${k2})`,`gm${sens}`),joinerPlaceholder+`(${this.processSynomyms(k1)}|`+`${this.processSynomyms(k2)})`+joinerPlaceholder);}}}return str;}processSynomyms(str){if(this.opt.ignoreJoiners||this.opt.ignorePunctuation.length){str=this.setupIgnoreJoinersRegExp(str);}return str;}setupWildcardsRegExp(str){str=str.replace(/(?:\\)*\?/g,val=>{return val.charAt(0)==="\\"?"?":"\u0001";});return str.replace(/(?:\\)*\*/g,val=>{return val.charAt(0)==="\\"?"*":"\u0002";});}createWildcardsRegExp(str){let spaces=this.opt.wildcards==="withSpaces";return str.replace(/\u0001/g,spaces?"[\\S\\s]?":"\\S?").replace(/\u0002/g,spaces?"[\\S\\s]*?":"\\S*");}setupIgnoreJoinersRegExp(str){return str.replace(/[^(|)\\]/g,(val,indx,original)=>{let nextChar=original.charAt(indx+1);if(/[(|)\\]/.test(nextChar)||nextChar===""){return val;}else{return val+"\u0000";}});}createJoinersRegExp(str){let joiner=[];const ignorePunctuation=this.opt.ignorePunctuation;if(Array.isArray(ignorePunctuation)&&ignorePunctuation.length){joiner.push(this.escapeStr(ignorePunctuation.join("")));}if(this.opt.ignoreJoiners){joiner.push("\\u00ad\\u200b\\u200c\\u200d");}return joiner.length?str.split(/\u0000+/).join(`[${joiner.join("")}]*`):str;}createDiacriticsRegExp(str){const sens=this.opt.caseSensitive?"":"i",dct=this.opt.caseSensitive?["aàáâãäåāąă","AÀÁÂÃÄÅĀĄĂ","cçćč","CÇĆČ","dđď","DĐĎ","eèéêëěēę","EÈÉÊËĚĒĘ","iìíîïī","IÌÍÎÏĪ","lł","LŁ","nñňń","NÑŇŃ","oòóôõöøō","OÒÓÔÕÖØŌ","rř","RŘ","sšśșş","SŠŚȘŞ","tťțţ","TŤȚŢ","uùúûüůū","UÙÚÛÜŮŪ","yÿý","YŸÝ","zžżź","ZŽŻŹ"]:["aàáâãäåāąăAÀÁÂÃÄÅĀĄĂ","cçćčCÇĆČ","dđďDĐĎ","eèéêëěēęEÈÉÊËĚĒĘ","iìíîïīIÌÍÎÏĪ","lłLŁ","nñňńNÑŇŃ","oòóôõöøōOÒÓÔÕÖØŌ","rřRŘ","sšśșşSŠŚȘŞ","tťțţTŤȚŢ","uùúûüůūUÙÚÛÜŮŪ","yÿýYŸÝ","zžżźZŽŻŹ"];let handled=[];str.split("").forEach(ch=>{dct.every(dct=>{if(dct.indexOf(ch)!==-1){if(handled.indexOf(dct)>-1){return false;}str=str.replace(new RegExp(`[${dct}]`,`gm${sens}`),`[${dct}]`);handled.push(dct);}return true;});});return str;}createMergedBlanksRegExp(str){return str.replace(/[\s]+/gmi,"[\\s]+");}createAccuracyRegExp(str){const chars=`!"#$%&'()*+,-./:;<=>?@[\\]^_\`{|}~¡¿`;let acc=this.opt.accuracy,val=typeof acc==="string"?acc:acc.value,ls=typeof acc==="string"?[]:acc.limiters,lsJoin="";ls.forEach(limiter=>{lsJoin+=`|${this.escapeStr(limiter)}`;});switch(val){case"partially":default:return`()(${str})`;case"complementary":lsJoin="\\s"+(lsJoin?lsJoin:this.escapeStr(chars));return`()([^${lsJoin}]*${str}[^${lsJoin}]*)`;case"exactly":return`(^|\\s${lsJoin})(${str})(?=$|\\s${lsJoin})`;}}getSeparatedKeywords(sv){let stack=[];sv.forEach(kw=>{if(!this.opt.separateWordSearch){if(kw.trim()&&stack.indexOf(kw)===-1){stack.push(kw);}}else{kw.split(" ").forEach(kwSplitted=>{if(kwSplitted.trim()&&stack.indexOf(kwSplitted)===-1){stack.push(kwSplitted);}});}});return{"keywords":stack.sort((a,b)=>{return b.length-a.length;}),"length":stack.length};}isNumeric(value){return Number(parseFloat(value))==value;}checkRanges(array){if(!Array.isArray(array)||Object.prototype.toString.call(array[0])!=="[object Object]"){this.log("markRanges() will only accept an array of objects");this.opt.noMatch(array);return[];}const stack=[];let last=0;array.sort((a,b)=>{return a.start-b.start;}).forEach(item=>{let{start,end,valid}=this.callNoMatchOnInvalidRanges(item,last);if(valid){item.start=start;item.length=end-start;stack.push(item);last=end;}});return stack;}callNoMatchOnInvalidRanges(range,last){let start,end,valid=false;if(range&&typeof range.start!=="undefined"){start=parseInt(range.start,10);end=start+parseInt(range.length,10);if(this.isNumeric(range.start)&&this.isNumeric(range.length)&&end-last>0&&end-start>0){valid=true;}else{this.log(`Ignoring invalid or overlapping range: `+`${JSON.stringify(range)}`);this.opt.noMatch(range);}}else{this.log(`Ignoring invalid range: ${JSON.stringify(range)}`);this.opt.noMatch(range);}return{start:start,end:end,valid:valid};}checkWhitespaceRanges(range,originalLength,string){let end,valid=true,max=string.length,offset=originalLength-max,start=parseInt(range.start,10)-offset;start=start>max?max:start;end=start+parseInt(range.length,10);if(end>max){end=max;this.log(`End range automatically set to the max value of ${max}`);}if(start<0||end-start<0||start>max||end>max){valid=false;this.log(`Invalid range: ${JSON.stringify(range)}`);this.opt.noMatch(range);}else if(string.substring(start,end).replace(/\s+/g,"")===""){valid=false;this.log("Skipping whitespace only range: "+JSON.stringify(range));this.opt.noMatch(range);}return{start:start,end:end,valid:valid};}getTextNodes(cb){let val="",nodes=[];this.iterator.forEachNode(NodeFilter.SHOW_TEXT,node=>{nodes.push({start:val.length,end:(val+=node.textContent).length,node});},node=>{if(this.matchesExclude(node.parentNode)){return NodeFilter.FILTER_REJECT;}else{return NodeFilter.FILTER_ACCEPT;}},()=>{cb({value:val,nodes:nodes});});}matchesExclude(el){return DOMIterator.matches(el,this.opt.exclude.concat(["script","style","title","head","html"]));}wrapRangeInTextNode(node,start,end){const hEl=!this.opt.element?"mark":this.opt.element,startNode=node.splitText(start),ret=startNode.splitText(end-start);let repl=document.createElement(hEl);repl.setAttribute("data-markjs","true");if(this.opt.className){repl.setAttribute("class",this.opt.className);}repl.textContent=startNode.textContent;startNode.parentNode.replaceChild(repl,startNode);return ret;}wrapRangeInMappedTextNode(dict,start,end,filterCb,eachCb){dict.nodes.every((n,i)=>{const sibl=dict.nodes[i+1];if(typeof sibl==="undefined"||sibl.start>start){if(!filterCb(n.node)){return false;}const s=start-n.start,e=(end>n.end?n.end:end)-n.start,startStr=dict.value.substr(0,n.start),endStr=dict.value.substr(e+n.start);n.node=this.wrapRangeInTextNode(n.node,s,e);dict.value=startStr+endStr;dict.nodes.forEach((k,j)=>{if(j>=i){if(dict.nodes[j].start>0&&j!==i){dict.nodes[j].start-=e;}dict.nodes[j].end-=e;}});end-=e;eachCb(n.node.previousSibling,n.start);if(end>n.end){start=n.end;}else{return false;}}return true;});}wrapMatches(regex,ignoreGroups,filterCb,eachCb,endCb){const matchIdx=ignoreGroups===0?0:ignoreGroups+1;this.getTextNodes(dict=>{dict.nodes.forEach(node=>{node=node.node;let match;while((match=regex.exec(node.textContent))!==null&&match[matchIdx]!==""){if(!filterCb(match[matchIdx],node)){continue;}let pos=match.index;if(matchIdx!==0){for(let i=1;i<matchIdx;i++){pos+=match[i].length;}}node=this.wrapRangeInTextNode(node,pos,pos+match[matchIdx].length);eachCb(node.previousSibling);regex.lastIndex=0;}});endCb();});}wrapMatchesAcrossElements(regex,ignoreGroups,filterCb,eachCb,endCb){const matchIdx=ignoreGroups===0?0:ignoreGroups+1;this.getTextNodes(dict=>{let match;while((match=regex.exec(dict.value))!==null&&match[matchIdx]!==""){let start=match.index;if(matchIdx!==0){for(let i=1;i<matchIdx;i++){start+=match[i].length;}}const end=start+match[matchIdx].length;this.wrapRangeInMappedTextNode(dict,start,end,node=>{return filterCb(match[matchIdx],node);},(node,lastIndex)=>{regex.lastIndex=lastIndex;eachCb(node);});}endCb();});}wrapRangeFromIndex(ranges,filterCb,eachCb,endCb){this.getTextNodes(dict=>{const originalLength=dict.value.length;ranges.forEach((range,counter)=>{let{start,end,valid}=this.checkWhitespaceRanges(range,originalLength,dict.value);if(valid){this.wrapRangeInMappedTextNode(dict,start,end,node=>{return filterCb(node,range,dict.value.substring(start,end),counter);},node=>{eachCb(node,range);});}});endCb();});}unwrapMatches(node){const parent=node.parentNode;let docFrag=document.createDocumentFragment();while(node.firstChild){docFrag.appendChild(node.removeChild(node.firstChild));}parent.replaceChild(docFrag,node);if(!this.ie){parent.normalize();}else{this.normalizeTextNode(parent);}}normalizeTextNode(node){if(!node){return;}if(node.nodeType===3){while(node.nextSibling&&node.nextSibling.nodeType===3){node.nodeValue+=node.nextSibling.nodeValue;node.parentNode.removeChild(node.nextSibling);}}else{this.normalizeTextNode(node.firstChild);}this.normalizeTextNode(node.nextSibling);}markRegExp(regexp,opt){this.opt=opt;this.log(`Searching with expression "${regexp}"`);let totalMatches=0,fn="wrapMatches";const eachCb=element=>{totalMatches++;this.opt.each(element);};if(this.opt.acrossElements){fn="wrapMatchesAcrossElements";}this[fn](regexp,this.opt.ignoreGroups,(match,node)=>{return this.opt.filter(node,match,totalMatches);},eachCb,()=>{if(totalMatches===0){this.opt.noMatch(regexp);}this.opt.done(totalMatches);});}mark(sv,opt){this.opt=opt;let totalMatches=0,fn="wrapMatches";const{keywords:kwArr,length:kwArrLen}=this.getSeparatedKeywords(typeof sv==="string"?[sv]:sv),sens=this.opt.caseSensitive?"":"i",handler=kw=>{let regex=new RegExp(this.createRegExp(kw),`gm${sens}`),matches=0;this.log(`Searching with expression "${regex}"`);this[fn](regex,1,(term,node)=>{return this.opt.filter(node,kw,totalMatches,matches);},element=>{matches++;totalMatches++;this.opt.each(element);},()=>{if(matches===0){this.opt.noMatch(kw);}if(kwArr[kwArrLen-1]===kw){this.opt.done(totalMatches);}else{handler(kwArr[kwArr.indexOf(kw)+1]);}});};if(this.opt.acrossElements){fn="wrapMatchesAcrossElements";}if(kwArrLen===0){this.opt.done(totalMatches);}else{handler(kwArr[0]);}}markRanges(rawRanges,opt){this.opt=opt;let totalMatches=0,ranges=this.checkRanges(rawRanges);if(ranges&&ranges.length){this.log("Starting to mark with the following ranges: "+JSON.stringify(ranges));this.wrapRangeFromIndex(ranges,(node,range,match,counter)=>{return this.opt.filter(node,range,match,counter);},(element,range)=>{totalMatches++;this.opt.each(element,range);},()=>{this.opt.done(totalMatches);});}else{this.opt.done(totalMatches);}}unmark(opt){this.opt=opt;let sel=this.opt.element?this.opt.element:"*";sel+="[data-markjs]";if(this.opt.className){sel+=`.${this.opt.className}`;}this.log(`Removal selector "${sel}"`);this.iterator.forEachNode(NodeFilter.SHOW_ELEMENT,node=>{this.unwrapMatches(node);},node=>{const matchesSel=DOMIterator.matches(node,sel),matchesExclude=this.matchesExclude(node);if(!matchesSel||matchesExclude){return NodeFilter.FILTER_REJECT;}else{return NodeFilter.FILTER_ACCEPT;}},this.opt.done);}}class DOMIterator{constructor(ctx,iframes=true,exclude=[],iframesTimeout=5000){this.ctx=ctx;this.iframes=iframes;this.exclude=exclude;this.iframesTimeout=iframesTimeout;}static matches(element,selector){const selectors=typeof selector==="string"?[selector]:selector,fn=element.matches||element.matchesSelector||element.msMatchesSelector||element.mozMatchesSelector||element.oMatchesSelector||element.webkitMatchesSelector;if(fn){let match=false;selectors.every(sel=>{if(fn.call(element,sel)){match=true;return false;}return true;});return match;}else{return false;}}getContexts(){let ctx,filteredCtx=[];if(typeof this.ctx==="undefined"||!this.ctx){ctx=[];}else if(NodeList.prototype.isPrototypeOf(this.ctx)){ctx=Array.prototype.slice.call(this.ctx);}else if(Array.isArray(this.ctx)){ctx=this.ctx;}else if(typeof this.ctx==="string"){ctx=Array.prototype.slice.call(document.querySelectorAll(this.ctx));}else{ctx=[this.ctx];}ctx.forEach(ctx=>{const isDescendant=filteredCtx.filter(contexts=>{return contexts.contains(ctx);}).length>0;if(filteredCtx.indexOf(ctx)===-1&&!isDescendant){filteredCtx.push(ctx);}});return filteredCtx;}getIframeContents(ifr,successFn,errorFn=()=>{}){let doc;try{const ifrWin=ifr.contentWindow;doc=ifrWin.document;if(!ifrWin||!doc){throw new Error("iframe inaccessible");}}catch(e){errorFn();}if(doc){successFn(doc);}}isIframeBlank(ifr){const bl="about:blank",src=ifr.getAttribute("src").trim(),href=ifr.contentWindow.location.href;return href===bl&&src!==bl&&src;}observeIframeLoad(ifr,successFn,errorFn){let called=false,tout=null;const listener=()=>{if(called){return;}called=true;clearTimeout(tout);try{if(!this.isIframeBlank(ifr)){ifr.removeEventListener("load",listener);this.getIframeContents(ifr,successFn,errorFn);}}catch(e){errorFn();}};ifr.addEventListener("load",listener);tout=setTimeout(listener,this.iframesTimeout);}onIframeReady(ifr,successFn,errorFn){try{if(ifr.contentWindow.document.readyState==="complete"){if(this.isIframeBlank(ifr)){this.observeIframeLoad(ifr,successFn,errorFn);}else{this.getIframeContents(ifr,successFn,errorFn);}}else{this.observeIframeLoad(ifr,successFn,errorFn);}}catch(e){errorFn();}}waitForIframes(ctx,done){let eachCalled=0;this.forEachIframe(ctx,()=>true,ifr=>{eachCalled++;this.waitForIframes(ifr.querySelector("html"),()=>{if(! --eachCalled){done();}});},handled=>{if(!handled){done();}});}forEachIframe(ctx,filter,each,end=()=>{}){let ifr=ctx.querySelectorAll("iframe"),open=ifr.length,handled=0;ifr=Array.prototype.slice.call(ifr);const checkEnd=()=>{if(--open<=0){end(handled);}};if(!open){checkEnd();}ifr.forEach(ifr=>{if(DOMIterator.matches(ifr,this.exclude)){checkEnd();}else{this.onIframeReady(ifr,con=>{if(filter(ifr)){handled++;each(con);}checkEnd();},checkEnd);}});}createIterator(ctx,whatToShow,filter){return document.createNodeIterator(ctx,whatToShow,filter,false);}createInstanceOnIframe(contents){return new DOMIterator(contents.querySelector("html"),this.iframes);}compareNodeIframe(node,prevNode,ifr){const compCurr=node.compareDocumentPosition(ifr),prev=Node.DOCUMENT_POSITION_PRECEDING;if(compCurr&prev){if(prevNode!==null){const compPrev=prevNode.compareDocumentPosition(ifr),after=Node.DOCUMENT_POSITION_FOLLOWING;if(compPrev&after){return true;}}else{return true;}}return false;}getIteratorNode(itr){const prevNode=itr.previousNode();let node;if(prevNode===null){node=itr.nextNode();}else{node=itr.nextNode()&&itr.nextNode();}return{prevNode,node};}checkIframeFilter(node,prevNode,currIfr,ifr){let key=false,handled=false;ifr.forEach((ifrDict,i)=>{if(ifrDict.val===currIfr){key=i;handled=ifrDict.handled;}});if(this.compareNodeIframe(node,prevNode,currIfr)){if(key===false&&!handled){ifr.push({val:currIfr,handled:true});}else if(key!==false&&!handled){ifr[key].handled=true;}return true;}if(key===false){ifr.push({val:currIfr,handled:false});}return false;}handleOpenIframes(ifr,whatToShow,eCb,fCb){ifr.forEach(ifrDict=>{if(!ifrDict.handled){this.getIframeContents(ifrDict.val,con=>{this.createInstanceOnIframe(con).forEachNode(whatToShow,eCb,fCb);});}});}iterateThroughNodes(whatToShow,ctx,eachCb,filterCb,doneCb){const itr=this.createIterator(ctx,whatToShow,filterCb);let ifr=[],elements=[],node,prevNode,retrieveNodes=()=>{({prevNode,node}=this.getIteratorNode(itr));return node;};while(retrieveNodes()){if(this.iframes){this.forEachIframe(ctx,currIfr=>{return this.checkIframeFilter(node,prevNode,currIfr,ifr);},con=>{this.createInstanceOnIframe(con).forEachNode(whatToShow,ifrNode=>elements.push(ifrNode),filterCb);});}elements.push(node);}elements.forEach(node=>{eachCb(node);});if(this.iframes){this.handleOpenIframes(ifr,whatToShow,eachCb,filterCb);}doneCb();}forEachNode(whatToShow,each,filter,done=()=>{}){const contexts=this.getContexts();let open=contexts.length;if(!open){done();}contexts.forEach(ctx=>{const ready=()=>{this.iterateThroughNodes(whatToShow,ctx,each,filter,()=>{if(--open<=0){done();}});};if(this.iframes){this.waitForIframes(ctx,ready);}else{ready();}});}}$.fn.mark=function(sv,opt){new Mark(this.get()).mark(sv,opt);return this;};$.fn.markRegExp=function(regexp,opt){new Mark(this.get()).markRegExp(regexp,opt);return this;};$.fn.markRanges=function(ranges,opt){new Mark(this.get()).markRanges(ranges,opt);return this;};$.fn.unmark=function(opt){new Mark(this.get()).unmark(opt);return this;};return $;},window,document);
@@ -0,0 +1,26 @@
1
+ /* Style the links inside the sidenav */
2
+ #highlightSidenav a {
3
+ position: fixed; /* Position them relative to the browser window */
4
+ left: -15px; /* Position them outside of the screen */
5
+ transition: 0.3s; /* Add transition on hover */
6
+ padding: 15px; /* 15px padding */
7
+ width: 50px; /* Set a specific width */
8
+ text-decoration: none; /* Remove underline */
9
+ font-size: 20px; /* Increase font size */
10
+ color: white; /* White text color */
11
+ border-radius: 0 5px 5px 0; /* Rounded corners on the top right and bottom right side */
12
+ }
13
+
14
+ #highlightSidenav a:hover {
15
+ left: 0; /* On mouse-over, make the elements appear as they should */
16
+ }
17
+
18
+ #highlightSidenav a, a:visited {
19
+ color: #D3D3D3 !important;
20
+ }
21
+
22
+ /* The about link: 20px from the top with a green background */
23
+ #h-mode {
24
+ top: 50%;
25
+ background-color: #4CAF50;
26
+ }
metadata ADDED
@@ -0,0 +1,119 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fragment_highlighter-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - PhlowerTeam
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-11-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.15'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.15'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bootstrap-sass
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.3.5
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.3.5
55
+ - !ruby/object:Gem::Dependency
56
+ name: jquery-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - phlowerteam@gmail.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Gemfile
78
+ - LICENSE
79
+ - README.md
80
+ - Rakefile
81
+ - bin/console
82
+ - bin/setup
83
+ - fragment_highlighter-rails.gemspec
84
+ - lib/fragment_highlighter/rails.rb
85
+ - lib/fragment_highlighter/rails/version.rb
86
+ - vendor/assets/javascripts/fragment-highlighter/fragment-highlighter.js
87
+ - vendor/assets/javascripts/fragment-highlighter/libs/classes/HView.js
88
+ - vendor/assets/javascripts/fragment-highlighter/libs/classes/HWindowCtrls.js
89
+ - vendor/assets/javascripts/fragment-highlighter/libs/classes/LS.js
90
+ - vendor/assets/javascripts/fragment-highlighter/libs/classes/Marker.js
91
+ - vendor/assets/javascripts/fragment-highlighter/libs/classes/Tools.js
92
+ - vendor/assets/javascripts/fragment-highlighter/libs/classes/UserSettings.js
93
+ - vendor/assets/javascripts/fragment-highlighter/libs/jquery.mark.es6.min.js
94
+ - vendor/assets/stylesheets/fragment-highlighter.css
95
+ homepage: https://github.com/phlowerteam/fragment_highlighter-rails
96
+ licenses: []
97
+ metadata: {}
98
+ post_install_message:
99
+ rdoc_options: []
100
+ require_paths:
101
+ - lib
102
+ required_ruby_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ required_rubygems_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ requirements: []
113
+ rubyforge_project:
114
+ rubygems_version: 2.6.13
115
+ signing_key:
116
+ specification_version: 4
117
+ summary: JavaScript UI library for highlighting text fragments on the page and saving
118
+ ones into localStorage
119
+ test_files: []