fragment_highlighter-rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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: []