├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── config-overrides.js ├── design ├── demo.png └── logo │ ├── FullColor_1280x1024.eps │ ├── FullColor_1280x1024.pdf │ ├── FullColor_1280x1024.svg │ ├── FullColor_1280x1024_300dpi.jpg │ ├── FullColor_1280x1024_72dpi.jpg │ ├── FullColor_1280x1024_72dpi.png │ ├── FullColor_IconOnly_1280x1024_72dpi.jpg │ ├── FullColor_TextOnly_1280x1024_72dpi.jpg │ ├── FullColor_TransparentBg_1280x1024_72dpi.png │ ├── Grayscale_1280x1024_72dpi.png │ ├── website_logo_solid_background.png │ └── website_logo_transparent_background.png ├── package.json ├── public ├── iframe.html ├── index.html ├── logo.svg └── manifest.json ├── src ├── App.tsx ├── Components │ └── MarkdownRender │ │ ├── IframeRender.jsx │ │ ├── MarkdownRender.jsx │ │ ├── MarkdownRender.scss │ │ └── overrides │ │ ├── index.js │ │ ├── math │ │ └── index.jsx │ │ └── pre │ │ └── index.jsx ├── index.tsx ├── lib │ └── classes │ │ ├── Context.ts │ │ ├── EventHandler.ts │ │ ├── HighlightPainter.ts │ │ ├── Marker.ts │ │ ├── SerializedRange.ts │ │ └── errors │ │ └── DeserializationError.ts ├── libroot.ts ├── react-app-env.d.ts ├── test.md └── utils │ ├── makeid.ts │ ├── markdown.ts │ └── showDialog.tsx ├── tsconfig.gen-dts.json ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | build 4 | /*.tgz -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .idea 2 | public 3 | src 4 | *.md 5 | *.txt 6 | *.jpg 7 | *.png 8 | *.ico 9 | *.tgz 10 | test 11 | dist 12 | config-overrides.js 13 | manifest.json 14 | design 15 | build/logo.svg 16 | tsconfig.json 17 | tsconfig.gen-dts.json 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ke Wang 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-marker 2 | 3 | ![logo](https://raw.githubusercontent.com/notelix/web-marker/develop/public/logo.svg) 4 | 5 | A web page highlighter that features 6 | * accurate serialization and deserialization which makes it possible to correctly restore the highlights, even if part of the web page has changed 7 | * nested highlighting 8 | * no runtime-dependency 9 | 10 | ![demo](./design/demo.png) 11 | 12 | # How to run 13 | ```bash 14 | git clone https://github.com/notelix/web-marker 15 | cd web-marker 16 | npm i 17 | npm start 18 | ``` 19 | 20 | # How to use 21 | ``` 22 | npm install @notelix/web-marker 23 | ``` 24 | 25 | ```javascript 26 | import {Marker} from "@notelix/web-marker" 27 | 28 | const marker = new Marker({ 29 | rootElement: document.body, 30 | eventHandler: { 31 | onHighlightClick: (context, element) => { 32 | marker.unpaint(context.serializedRange); 33 | }, 34 | onHighlightHoverStateChange: (context, element, hovering) => { 35 | if (hovering) { 36 | element.style.backgroundColor = "#f0d8ff"; 37 | } else { 38 | element.style.backgroundColor = ""; 39 | } 40 | } 41 | }, 42 | highlightPainter: { 43 | paintHighlight: (context, element) => { 44 | element.style.color = "red"; 45 | } 46 | } 47 | }); 48 | 49 | marker.addEventListeners(); 50 | 51 | document.addEventListener('mouseup', (e) => { 52 | const selection = window.getSelection(); 53 | if (!selection.rangeCount) { 54 | return null; 55 | } 56 | const serialized = marker.serializeRange(selection.getRangeAt(0)); 57 | console.log(JSON.stringify(serialized)); 58 | marker.paint(serialized); 59 | }) 60 | ``` 61 | 62 | # How to build library 63 | ``` 64 | npm run build-lib 65 | npm pack 66 | ``` 67 | 68 | # Built with web-marker 69 | 70 | * [notelix/notelix](https://github.com/notelix/notelix): An open source web note taking / highlighter software (chrome extension with backend) 71 | -------------------------------------------------------------------------------- /config-overrides.js: -------------------------------------------------------------------------------- 1 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; 2 | const path = require("path"); 3 | const CopyPlugin = require("copy-webpack-plugin"); 4 | 5 | module.exports = function override(config, env) { 6 | config.plugins.push(new CopyPlugin({ 7 | patterns: [ 8 | {from: 'public', to: 'public'} 9 | ] 10 | })) 11 | if (process.env.NO_BUNDLE_ANALYSER !== "true") { 12 | config.plugins.push(new BundleAnalyzerPlugin()); 13 | } 14 | if (process.env.BUILD_TARGET === "lib") { 15 | config.entry = [ 16 | path.resolve(process.cwd(), "src", "libroot.ts") 17 | ] 18 | 19 | config.output = { 20 | path: config.output.path, 21 | filename: 'web-marker.js', 22 | library: "web-marker", 23 | libraryTarget: "umd", 24 | } 25 | 26 | delete config.optimization["splitChunks"]; 27 | delete config.optimization["runtimeChunk"]; 28 | config.externals = ["react", "react-dom"]; 29 | config.plugins = config.plugins.filter(p => ["HtmlWebpackPlugin", "GenerateSW", "ManifestPlugin"].indexOf(p.constructor.name) < 0) 30 | } 31 | return config; 32 | } 33 | -------------------------------------------------------------------------------- /design/demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/demo.png -------------------------------------------------------------------------------- /design/logo/FullColor_1280x1024.eps: -------------------------------------------------------------------------------- 1 | %!PS-Adobe-3.0 EPSF-3.0 2 | %%Creator: cairo 1.16.0 (https://cairographics.org) 3 | %%CreationDate: Tue Nov 3 10:50:40 2020 4 | %%Pages: 1 5 | %%DocumentData: Clean7Bit 6 | %%LanguageLevel: 3 7 | %%BoundingBox: 0 0 961 769 8 | %%EndComments 9 | %%BeginProlog 10 | 50 dict begin 11 | /q { gsave } bind def 12 | /Q { grestore } bind def 13 | /cm { 6 array astore concat } bind def 14 | /w { setlinewidth } bind def 15 | /J { setlinecap } bind def 16 | /j { setlinejoin } bind def 17 | /M { setmiterlimit } bind def 18 | /d { setdash } bind def 19 | /m { moveto } bind def 20 | /l { lineto } bind def 21 | /c { curveto } bind def 22 | /h { closepath } bind def 23 | /re { exch dup neg 3 1 roll 5 3 roll moveto 0 rlineto 24 | 0 exch rlineto 0 rlineto closepath } bind def 25 | /S { stroke } bind def 26 | /f { fill } bind def 27 | /f* { eofill } bind def 28 | /n { newpath } bind def 29 | /W { clip } bind def 30 | /W* { eoclip } bind def 31 | /BT { } bind def 32 | /ET { } bind def 33 | /BDC { mark 3 1 roll /BDC pdfmark } bind def 34 | /EMC { mark /EMC pdfmark } bind def 35 | /cairo_store_point { /cairo_point_y exch def /cairo_point_x exch def } def 36 | /Tj { show currentpoint cairo_store_point } bind def 37 | /TJ { 38 | { 39 | dup 40 | type /stringtype eq 41 | { show } { -0.001 mul 0 cairo_font_matrix dtransform rmoveto } ifelse 42 | } forall 43 | currentpoint cairo_store_point 44 | } bind def 45 | /cairo_selectfont { cairo_font_matrix aload pop pop pop 0 0 6 array astore 46 | cairo_font exch selectfont cairo_point_x cairo_point_y moveto } bind def 47 | /Tf { pop /cairo_font exch def /cairo_font_matrix where 48 | { pop cairo_selectfont } if } bind def 49 | /Td { matrix translate cairo_font_matrix matrix concatmatrix dup 50 | /cairo_font_matrix exch def dup 4 get exch 5 get cairo_store_point 51 | /cairo_font where { pop cairo_selectfont } if } bind def 52 | /Tm { 2 copy 8 2 roll 6 array astore /cairo_font_matrix exch def 53 | cairo_store_point /cairo_font where { pop cairo_selectfont } if } bind def 54 | /g { setgray } bind def 55 | /rg { setrgbcolor } bind def 56 | /d1 { setcachedevice } bind def 57 | /cairo_data_source { 58 | CairoDataIndex CairoData length lt 59 | { CairoData CairoDataIndex get /CairoDataIndex CairoDataIndex 1 add def } 60 | { () } ifelse 61 | } def 62 | /cairo_flush_ascii85_file { cairo_ascii85_file status { cairo_ascii85_file flushfile } if } def 63 | /cairo_image { image cairo_flush_ascii85_file } def 64 | /cairo_imagemask { imagemask cairo_flush_ascii85_file } def 65 | %%EndProlog 66 | %%BeginSetup 67 | %%EndSetup 68 | %%Page: 1 1 69 | %%BeginPageSetup 70 | %%PageBoundingBox: 0 0 961 769 71 | %%EndPageSetup 72 | q 0 0 961 769 rectclip 73 | 1 0 0 -1 0 769 cm q 74 | 1 g 75 | 0 0 960.023 768.02 re f 76 | 0.964706 0.721569 0.0431373 rg 77 | 247.145 401.801 m 346.629 369.957 l 404.332 142.211 l 304.844 174.047 l 78 | 247.145 401.801 l f 79 | 0.352941 0.317647 0.67451 rg 80 | 530.52 139.84 m 629.996 107.996 l 712.867 353.129 l 615.531 393.867 l 530.52 81 | 139.84 l f 82 | 0.945098 0.933333 0.101961 rg 83 | 362.238 308.852 m 404.449 142.254 l 540.238 461.723 l 440.75 493.551 l 84 | 362.238 308.852 l f 85 | 0.929412 0.666667 0.0588235 rg 86 | 404.359 142.168 m 304.867 174.004 l 362.148 308.766 l h 87 | 404.359 142.168 m f 88 | 0.317647 0.494118 0.788235 rg 89 | 440.828 493.535 m 540.316 461.707 l 629.914 108.039 l 530.438 139.883 l 90 | 440.828 493.535 l f 91 | 0.364706 0.772549 0.521569 rg 92 | 440.789 493.637 m 540.277 461.809 l 482.996 327.047 l h 93 | 440.789 493.637 m f 94 | 0.172549 0.215686 0.54902 rg 95 | 629.957 107.949 m 530.477 139.793 l 582.5 295.258 l h 96 | 629.957 107.949 m f 97 | q 98 | 302.145 590.699 m 293.711 590.699 l 288.809 563.332 l 283.562 590.699 l 99 | 275.336 590.699 l 268.602 553.336 l 274.629 553.336 l 279.457 587.008 l 100 | 285.41 558.09 l 292.426 558.09 l 298.023 587.008 l 302.852 553.336 l 308.598 101 | 553.336 l h 102 | 321.715 574.113 m 321.852 578.223 322.926 581.316 324.934 583.406 c 326.941 103 | 585.484 329.484 586.52 332.555 586.52 c 334.395 586.52 336.086 586.25 337.621 104 | 585.711 c 339.156 585.156 340.801 584.316 342.555 583.184 c 345.316 587.156 105 | l 343.562 588.523 341.562 589.59 339.32 590.348 c 337.074 591.105 334.82 106 | 591.484 332.555 591.484 c 327.168 591.484 322.973 589.723 319.973 586.195 107 | c 316.969 582.68 315.465 577.969 315.465 572.062 c 315.465 568.32 316.133 108 | 564.973 317.461 562.02 c 318.781 559.066 320.672 556.75 323.133 555.078 109 | c 325.582 553.402 328.438 552.566 331.699 552.566 c 336.426 552.566 340.148 110 | 554.207 342.863 557.484 c 345.582 560.773 346.941 565.254 346.941 570.926 111 | c 346.941 572.008 346.895 573.07 346.809 574.113 c h 112 | 331.773 557.453 m 328.887 557.453 326.547 558.484 324.758 560.543 c 322.965 113 | 562.59 321.949 565.602 321.715 569.582 c 341.137 569.582 l 341.086 565.652 114 | 340.234 562.648 338.582 560.57 c 336.926 558.492 334.656 557.453 331.773 115 | 557.453 c h 116 | 365.445 558.238 m 368.332 554.457 371.902 552.566 376.156 552.566 c 380.93 117 | 552.566 384.449 554.266 386.715 557.66 c 388.988 561.07 390.125 565.844 118 | 390.125 571.988 c 390.125 577.848 388.828 582.559 386.227 586.121 c 383.629 119 | 589.695 379.984 591.484 375.297 591.484 c 371.004 591.484 367.629 589.922 120 | 365.168 586.801 c 364.738 590.699 l 359.496 590.699 l 359.496 538.312 l 121 | 365.445 537.605 l h 122 | 373.953 586.652 m 377.074 586.652 379.473 585.43 381.148 582.977 c 382.832 123 | 580.516 383.672 576.852 383.672 571.988 c 383.672 567.164 382.93 563.523 124 | 381.441 561.074 c 379.945 558.613 377.734 557.383 374.812 557.383 c 372.922 125 | 557.383 371.184 557.945 369.598 559.078 c 368.012 560.211 366.629 561.629 126 | 365.445 563.332 c 365.445 581.84 l 366.441 583.355 367.672 584.539 369.141 127 | 585.383 c 370.605 586.23 372.211 586.652 373.953 586.652 c h 128 | 403.805 571.059 m 403.805 565.961 l 428.617 565.961 l 428.617 571.059 l 129 | h 130 | 469.172 552.566 m 471.301 552.566 473.035 553.309 474.371 554.797 c 475.723 131 | 556.281 476.395 558.984 476.395 562.906 c 476.395 590.699 l 470.945 590.699 132 | l 470.945 563.91 l 470.945 561.309 470.777 559.547 470.445 558.621 c 470.109 133 | 557.695 469.305 557.234 468.035 557.234 c 465.625 557.234 463.449 558.727 134 | 461.508 561.707 c 461.508 590.699 l 455.984 590.699 l 455.984 563.91 l 135 | 455.984 561.309 455.816 559.547 455.48 558.621 c 455.156 557.695 454.355 136 | 557.234 453.074 557.234 c 450.664 557.234 448.488 558.727 446.547 561.707 137 | c 446.547 590.699 l 441.098 590.699 l 441.098 553.336 l 445.703 553.336 138 | l 446.133 557.734 l 447.266 556.082 448.457 554.805 449.707 553.91 c 450.957 139 | 553.016 452.461 552.566 454.211 552.566 c 457.707 552.566 459.953 554.219 140 | 460.945 557.527 c 462.078 555.914 463.285 554.684 464.566 553.836 c 465.836 141 | 552.988 467.371 552.566 469.172 552.566 c h 142 | 513.895 582.547 m 513.895 584.016 514.133 585.09 514.605 585.77 c 515.078 143 | 586.457 515.859 586.969 516.953 587.305 c 515.461 591.559 l 511.816 591.086 144 | 509.547 589.312 508.652 586.242 c 507.332 587.934 505.668 589.234 503.66 145 | 590.141 c 501.652 591.035 499.418 591.484 496.957 591.484 c 493.223 591.484 146 | 490.285 590.445 488.137 588.367 c 485.98 586.281 484.902 583.488 484.902 147 | 579.992 c 484.902 576.164 486.406 573.211 489.406 571.133 c 492.41 569.055 148 | 496.723 568.016 502.348 568.016 c 507.871 568.016 l 507.871 564.973 l 507.871 149 | 562.324 507.117 560.418 505.609 559.258 c 504.094 558.105 501.895 557.527 150 | 499.008 557.527 c 496.172 557.527 492.938 558.164 489.305 559.434 c 487.664 151 | 554.898 l 492.066 553.344 496.156 552.566 499.938 552.566 c 504.477 552.566 152 | 507.938 553.629 510.32 555.758 c 512.703 557.883 513.895 560.836 513.895 153 | 564.617 c h 154 | 498.445 587.008 m 500.289 587.008 502.051 586.535 503.734 585.59 c 505.41 155 | 584.645 506.785 583.348 507.871 581.691 c 507.871 572.121 l 502.492 572.121 156 | l 498.516 572.121 495.656 572.785 493.914 574.113 c 492.16 575.434 491.285 157 | 577.371 491.285 579.918 c 491.285 584.645 493.672 587.008 498.445 587.008 158 | c h 159 | 554.883 552.566 m 556.387 552.566 558.137 552.82 560.125 553.336 c 559.27 160 | 566.035 l 554.453 566.035 l 554.453 558.016 l 554.098 558.016 l 548.328 161 | 558.016 544.242 562.125 541.84 570.348 c 541.84 586.094 l 549.418 586.094 162 | l 549.418 590.699 l 529.996 590.699 l 529.996 586.094 l 535.871 586.094 163 | l 535.871 557.941 l 529.996 557.941 l 529.996 553.336 l 540.348 553.336 164 | l 541.484 562.195 l 542.992 558.984 544.797 556.578 546.906 554.973 c 549.004 165 | 553.367 551.66 552.566 554.883 552.566 c h 166 | 578.559 537.605 m 578.559 590.699 l 572.605 590.699 l 572.605 538.312 l 167 | h 168 | 603.445 553.336 m 586.711 570.215 l 605.422 590.699 l 597.551 590.699 l 169 | 579.191 570.289 l 595.777 553.336 l h 170 | 619.469 574.113 m 619.605 578.223 620.68 581.316 622.688 583.406 c 624.699 171 | 585.484 627.238 586.52 630.309 586.52 c 632.152 586.52 633.84 586.25 635.375 172 | 585.711 c 636.91 585.156 638.555 584.316 640.309 583.184 c 643.07 587.156 173 | l 641.316 588.523 639.32 589.59 637.074 590.348 c 634.828 591.105 632.574 174 | 591.484 630.309 591.484 c 624.922 591.484 620.73 589.723 617.727 586.195 175 | c 614.723 582.68 613.223 577.969 613.223 572.062 c 613.223 568.32 613.887 176 | 564.973 615.215 562.02 c 616.535 559.066 618.426 556.75 620.887 555.078 177 | c 623.34 553.402 626.195 552.566 629.453 552.566 c 634.18 552.566 637.902 178 | 554.207 640.617 557.484 c 643.336 560.773 644.695 565.254 644.695 570.926 179 | c 644.695 572.008 644.652 573.07 644.562 574.113 c h 180 | 629.527 557.453 m 626.641 557.453 624.305 558.484 622.512 560.543 c 620.719 181 | 562.59 619.707 565.602 619.469 569.582 c 638.891 569.582 l 638.84 565.652 182 | 637.988 562.648 636.336 560.57 c 634.68 558.492 632.41 557.453 629.527 183 | 557.453 c h 184 | 682.492 552.566 m 683.996 552.566 685.746 552.82 687.734 553.336 c 686.879 185 | 566.035 l 682.062 566.035 l 682.062 558.016 l 681.707 558.016 l 675.938 186 | 558.016 671.852 562.125 669.449 570.348 c 669.449 586.094 l 677.027 586.094 187 | l 677.027 590.699 l 657.605 590.699 l 657.605 586.094 l 663.48 586.094 188 | l 663.48 557.941 l 657.605 557.941 l 657.605 553.336 l 667.957 553.336 l 189 | 669.094 562.195 l 670.602 558.984 672.406 556.578 674.516 554.973 c 676.613 190 | 553.367 679.27 552.566 682.492 552.566 c h 191 | 682.492 552.566 m W n 192 | [1.476959 0 0 1.476959 268.602631 537.604497] concat 193 | /CairoFunction 194 | << /FunctionType 2 195 | /Domain [ 0 1 ] 196 | /C0 [ 0.972549 0.301961 0.619608 ] 197 | /C1 [ 1 0.454902 0.458824 ] 198 | /N 1 199 | >> 200 | def 201 | << /ShadingType 2 202 | /ColorSpace /DeviceRGB 203 | /Coords [ 0 0 0 54 ] 204 | /Extend [ true true ] 205 | /Function CairoFunction 206 | >> 207 | shfill 208 | Q 209 | Q Q 210 | showpage 211 | %%Trailer 212 | end 213 | %%EOF 214 | -------------------------------------------------------------------------------- /design/logo/FullColor_1280x1024.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/FullColor_1280x1024.pdf -------------------------------------------------------------------------------- /design/logo/FullColor_1280x1024.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Fabric.js 3.6.3 5 | 6 | 7 | 10 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /design/logo/FullColor_1280x1024_300dpi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/FullColor_1280x1024_300dpi.jpg -------------------------------------------------------------------------------- /design/logo/FullColor_1280x1024_72dpi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/FullColor_1280x1024_72dpi.jpg -------------------------------------------------------------------------------- /design/logo/FullColor_1280x1024_72dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/FullColor_1280x1024_72dpi.png -------------------------------------------------------------------------------- /design/logo/FullColor_IconOnly_1280x1024_72dpi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/FullColor_IconOnly_1280x1024_72dpi.jpg -------------------------------------------------------------------------------- /design/logo/FullColor_TextOnly_1280x1024_72dpi.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/FullColor_TextOnly_1280x1024_72dpi.jpg -------------------------------------------------------------------------------- /design/logo/FullColor_TransparentBg_1280x1024_72dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/FullColor_TransparentBg_1280x1024_72dpi.png -------------------------------------------------------------------------------- /design/logo/Grayscale_1280x1024_72dpi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/Grayscale_1280x1024_72dpi.png -------------------------------------------------------------------------------- /design/logo/website_logo_solid_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/website_logo_solid_background.png -------------------------------------------------------------------------------- /design/logo/website_logo_transparent_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notelix/web-marker/8c4609de4ceeb9f5f8f402137d02bf6326ab39c9/design/logo/website_logo_transparent_background.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@notelix/web-marker", 3 | "version": "2.0.6", 4 | "description": "a web page highlighter", 5 | "main": "./build/web-marker.js", 6 | "types": "./build/libroot.d.ts", 7 | "scripts": { 8 | "start": "react-app-rewired start", 9 | "build": "react-app-rewired build", 10 | "build-lib": "BUILD_TARGET=lib react-app-rewired build", 11 | "prepublish": "NO_BUNDLE_ANALYSER=true npm run build-lib; tsc -p tsconfig.gen-dts.json", 12 | "test": "react-app-rewired test", 13 | "eject": "react-app-rewired eject" 14 | }, 15 | "dependencies": { 16 | "uuid": "^8.3.2" 17 | }, 18 | "homepage": "https://github.com/notelix/web-marker", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/notelix/web-marker" 22 | }, 23 | "devDependencies": { 24 | "@material-ui/core": "^4.11.0", 25 | "@types/react-dom": "^17.0.9", 26 | "@types/uuid": "^8.3.1", 27 | "bulma": "^0.9.1", 28 | "copy-webpack-plugin": "6", 29 | "lodash-es": "^4.17.15", 30 | "markdown-to-jsx": "^7.0.1", 31 | "raw-loader": "^4.0.1", 32 | "react": "^17.0.0", 33 | "react-app-rewired": "^2.1.6", 34 | "react-dom": "^17.0.0", 35 | "react-katex": "^2.0.2", 36 | "react-scripts": "^3.4.1", 37 | "react-syntax-highlighter": "^15.3.0", 38 | "sass": "^1.38.0", 39 | "typescript": "^4.0.5", 40 | "webpack-bundle-analyzer": "^4.4.2", 41 | "yaml": "^1.10.0" 42 | }, 43 | "keywords": [ 44 | "web", 45 | "marker", 46 | "text", 47 | "highlight", 48 | "annotation", 49 | "selection", 50 | "range" 51 | ], 52 | "author": "Ke Wang <453587854@qq.com>", 53 | "license": "MIT", 54 | "browserslist": { 55 | "production": [ 56 | ">0.2%", 57 | "not dead", 58 | "not op_mini all" 59 | ], 60 | "development": [ 61 | "last 1 chrome version", 62 | "last 1 firefox version", 63 | "last 1 safari version" 64 | ] 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /public/iframe.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | iframe 8 | 9 | 10 | 11 | 12 |

this is test iframe page

13 |
14 |

Promethides lupum radicis pectora stagnata lingua

15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 |
TablesAreCool
col 3 isright-aligned$1600
col 2 iscentered 33 | $12
zebra stripesare 40 | neat$1
45 |

title 1

46 |

text

47 |

title 2

48 |

text

49 |

title 3

50 |

text

51 |

title 4

52 |

text

53 |

Fata exceptas

54 |

Lorem markdownum asdqwe fdsg integer . Et 55 | fatis, Nabataeus illo curvis 56 | quos menti, quam iussi, superator 57 | cupiam sibi nostra. Flector omnia verticis et Iovis desiluit inopem huius;

58 |

Lorem markdownum asdqwe fdsg integer . Et 59 | fatis, Nabataeus illo curvis 60 | quos menti, quam iussi, superator 61 | cupiam sibi nostra. Flector omnia verticis et Iovis desiluit inopem huius;

62 |

Lorem markdownum asdqwe fdsg integer . Et 63 | fatis, Nabataeus illo curvis 64 | quos menti, quam iussi, superator 65 | cupiam sibi nostra. Flector omnia verticis et Iovis desiluit inopem huius;

66 |

patris quem: uvae est en sol. Corpora simul, et limine lacrimae letale Circes, 67 | licet saeva praemia cingentibus, has suscitat fiducia et aa bb ramis morata. Aquas 68 | ultra pictas tenent ab atque et et, sancta.

69 |
{
 70 |   "a": 1
 71 | }
 72 | 
73 |
{
 74 |   "a": 1
 75 | }
 76 | 
77 |
if (8 != pixelMinisiteListserv) {
 78 |   im.kilohertz(ioPeripheral, memoryBatch, cdn);
 79 |   wi_parse = baseband;
 80 |   bareFloatingSmm(fios);
 81 | } else {
 82 |   expansion = data(cut - 64, 2 + sata);
 83 |   pingTftp.port = log;
 84 |   vertical.abendVrml(5);
 85 | }
 86 | compression *= office.device(4, parallelMetafile, storageCardBoot);
 87 | if (zoneAlphaDesign(kvm, vdu, web) - southbridge) {
 88 |   microcomputer_computing_map += pramSpeed(wddm + iscsi);
 89 |   jsfJumper.ssl_page -= phishing;
 90 |   joystick_plagiarism += ipad(parameter, 53);
 91 | } else {
 92 |   threading_traceroute = 1;
 93 |   syn(lpi, pmu);
 94 |   wais_hard.repeaterIpPda -= bingUrlReadme;
 95 | }
 96 | 
97 |

img

98 |

Genus sit movit tulerunt fortius qui, in imagine igitur concussit, est 99 | fluitantia findit; precor sed. Reliquit perforat Tartara, inter caput facitis 100 | Cadmeida et taurus pharetratus danda 101 | penates, manente. Ventis iam enim onerosa, nisi, dea viri prius volitat: capilli 102 | aevum cum terrae, imagine? In gloria gentis territa itque: arboreis dicenda, 103 | omnia baculi, resupinus tenentem. Medusaeo genus; Aiax in vocant quaeque 104 | erroribus futura, Haemonio inquit; alte.

105 |

An tum

106 |

Hello <code> abc </code>

107 |

Scopulum pavens seductus, prensantem surgere: dubites deus pectora? Pone 108 | tria quae foret pervenit pulsat: origine: ipsorum, aut! 109 | Ululatibus vidisset parte nuribus: 110 | Pirithoum nata cladis ipse enim 111 | gerentes potest sumat iam; verba iubar fisso invitumque. Pendeat libatos et 112 | mille tibi movebere quod mox figuram si? 113 |

114 |
    115 |
  1. Et digitis modo minores portas aequatam
  2. 116 |
  3. Quae nam Phoebus metues
  4. 117 |
  5. Spiritus figuras
  6. 118 |
  7. Et virgo
  8. 119 |
  9. Nostris agros est
  10. 120 |
121 |

Venus dat aut exsecratur moveat facit ipse igne, nostras quid tenet, est nec 122 | sum. Albis laborum, per perit superata, scissae magis, loci nec placidi 123 | experiensque voluit Diomedeos summa: aeolis qua. Et 124 | superare tamen. Plurima in simul cornibus magni muta illac praestem querella 125 | accessus candidus.

126 |

Ecce denegat quantus. Et adest priorum de suae animosus alba subduxit _alternae 127 | tetenderat_ deos quid ferrumque sumptaque constituit spargimur. Oleaster 128 | pignora, Cephalus vibrant; silet ante quadrupes Cyllenius portus Styga sepulcro 129 | subdit dissimulator forte Iovis indigestaque turba pinus.

130 |
131 | 132 | 133 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 16 | 17 | 26 | web-marker 27 | 28 | 29 | 30 |
31 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Created with Fabric.js 3.6.3 5 | 6 | 7 | 10 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Web Marker", 3 | "name": "", 4 | "icons": [], 5 | "start_url": ".", 6 | "display": "standalone", 7 | "theme_color": "#000000", 8 | "background_color": "#ffffff" 9 | } 10 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | // @ts-ignore 3 | // eslint-disable-next-line import/no-webpack-loader-syntax 4 | import testMd from "!raw-loader!./test.md"; 5 | import MarkdownRender from "./Components/MarkdownRender/MarkdownRender"; 6 | import IframeRender from "./Components/MarkdownRender/IframeRender"; 7 | 8 | class App extends React.Component { 9 | private isIframe: boolean; 10 | 11 | constructor(props: any) { 12 | super(props) 13 | const urlSearchParams = new URLSearchParams(window.location.search.slice(1)); 14 | this.isIframe = urlSearchParams.has('iframe'); 15 | } 16 | 17 | render() { 18 | return this.isIframe 19 | ? 20 | : ; 21 | } 22 | } 23 | 24 | export default App; 25 | -------------------------------------------------------------------------------- /src/Components/MarkdownRender/IframeRender.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./MarkdownRender.scss"; 4 | import TextareaAutosize from "@material-ui/core/TextareaAutosize"; 5 | import Button from "@material-ui/core/Button"; 6 | import Marker from "../../lib/classes/Marker"; 7 | import showDialog from "../../utils/showDialog"; 8 | import makeid from "../../utils/makeid"; 9 | 10 | const demoMultiRangeMode = window.location.hash.indexOf("multi-range") >= 0; 11 | 12 | const LOCALSTORAGE_HIGHLIGHTS_KEY = "web-marker-highlights-iframe"; 13 | 14 | class AnnotationDialog extends React.Component { 15 | static DialogTitle = "Annotation"; 16 | state = { text: this.props.text || "" }; 17 | 18 | DialogButtons = ({ closeDialog }) => [ 19 | , 26 | ]; 27 | 28 | render() { 29 | return ( 30 |
31 | { 35 | this.setState({ text: e.target.value }); 36 | }} 37 | /> 38 |
39 | ); 40 | } 41 | } 42 | 43 | function findProperTopLeftAndWidth(clientRect) { 44 | if (!clientRect) { 45 | return [0, 0]; 46 | } 47 | let top = clientRect.top - 35; 48 | if (top - window.scrollY < 0) { 49 | top = window.scrollY; 50 | } 51 | let width = Math.min(clientRect.width, window.innerWidth); 52 | width = Math.max(width, 300); 53 | let left = clientRect.left + clientRect.width / 2 - width / 2; 54 | if (left < 10) { 55 | left = 10; 56 | } 57 | if (left + width > window.innerWidth - 10) { 58 | left += window.innerWidth - 10 - (left + width); 59 | } 60 | return [top, left, width]; 61 | } 62 | 63 | class IframeRender extends React.Component { 64 | highlights = {}; 65 | mapHighlightIdToRange = {}; 66 | selectedHighlightId = null; 67 | 68 | iframeRef = React.createRef(); 69 | 70 | state = { userSelection: {}, hideHighlightButtons: true }; 71 | 72 | loadHighlightsFromLocalStorage() { 73 | const data = JSON.parse(localStorage[LOCALSTORAGE_HIGHLIGHTS_KEY]); 74 | Object.keys(data).forEach((key) => { 75 | data[key].uid = key; 76 | }); 77 | return data; 78 | } 79 | 80 | saveHighlightsToLocalStorage() { 81 | if (demoMultiRangeMode) { 82 | return; 83 | } 84 | const toSave = {}; 85 | Object.keys(this.highlights).forEach((key) => { 86 | toSave[key] = { ...this.highlights[key], id: undefined }; 87 | }); 88 | localStorage[LOCALSTORAGE_HIGHLIGHTS_KEY] = JSON.stringify(toSave); 89 | } 90 | 91 | 92 | 93 | paint(serializedRange) { 94 | this.marker.paint(serializedRange); 95 | Marker.clearSelection(this.marker.window); 96 | this.highlights[serializedRange.uid] = serializedRange; 97 | this.mapHighlightIdToRange[serializedRange.uid] = this.marker.deserializeRange( 98 | serializedRange 99 | ); 100 | this.saveHighlightsToLocalStorage(); 101 | } 102 | 103 | setUserSelectionByRange(range) { 104 | const iframePos = this.iframeRef.current.getBoundingClientRect(); 105 | let pos = range.getBoundingClientRect(); 106 | 107 | let calculatedPos = { 108 | top: pos.top + window.scrollY + iframePos.top, 109 | left: pos.left + window.scrollX + iframePos.left, 110 | width: pos.width, 111 | height: pos.height, 112 | }; 113 | if (calculatedPos.width === 0) { 114 | calculatedPos.width = 400; 115 | calculatedPos.top = window.pointerPos.y - 10; 116 | calculatedPos.left = window.pointerPos.x - 200; 117 | } 118 | this.setState({ 119 | userSelection: { 120 | range, 121 | clientRect: calculatedPos, 122 | }, 123 | hideHighlightButtons: false, 124 | }); 125 | } 126 | 127 | mouseMoveListener = (e) => { 128 | const { pageX, pageY } = e; 129 | window.pointerPos = { x: pageX, y: pageY }; 130 | }; 131 | mouseupListener = (e) => { 132 | setTimeout(() => { 133 | this.handleMouseUp(e, false); 134 | }); 135 | }; 136 | 137 | handleMouseUp = (e, calledRecursively = false) => { 138 | const contentWindow = this.iframeRef.current.contentWindow; 139 | try { 140 | const selection = contentWindow.getSelection(); 141 | if (!selection.toString() && !demoMultiRangeMode) { 142 | this.setState({ hideHighlightButtons: true }); 143 | return; 144 | } 145 | 146 | if (!selection.rangeCount) { 147 | return null; 148 | } 149 | 150 | let range = null; 151 | try { 152 | range = selection.getRangeAt(0); 153 | } catch (e) {} 154 | if (!range) { 155 | return null; 156 | } 157 | 158 | this.selectedHighlightId = null; 159 | 160 | this.setUserSelectionByRange(range); 161 | if (demoMultiRangeMode) { 162 | const serialized = this.marker.serializeRange( 163 | this.state.userSelection.range 164 | ); 165 | if (!serialized) { 166 | return; 167 | } 168 | this.paint(serialized); 169 | } 170 | } finally { 171 | if (demoMultiRangeMode) { 172 | const selection = contentWindow.getSelection(); 173 | if (selection.isCollapsed && !this.isHighlightElement(e.target)) { 174 | selection.modify("move", "forward", "character"); 175 | selection.modify("extend", "backward", "word"); 176 | selection.modify("move", "backward", "character"); 177 | selection.modify("extend", "forward", "word"); 178 | if (!calledRecursively) { 179 | this.handleMouseUp(e, true); 180 | } 181 | } 182 | setTimeout(() => { 183 | this.setState({ 184 | hideHighlightButtons: Object.keys(this.highlights).length === 0, 185 | }); 186 | }); 187 | } 188 | } 189 | }; 190 | 191 | componentDidMount() { 192 | this.iframeRef.current.onload = () => { 193 | const contentWindow = this.iframeRef.current.contentWindow; 194 | this.marker = new Marker({ 195 | rootElement: contentWindow.document.body, 196 | eventHandler: { 197 | onHighlightClick: (context, element) => { 198 | setTimeout(() => { 199 | if (demoMultiRangeMode) { 200 | // click again to delete in multi range mode 201 | this.marker.unpaint(this.highlights[context.serializedRange.uid]); 202 | delete this.highlights[context.serializedRange.uid]; 203 | } else { 204 | this.selectedHighlightId = context.serializedRange.uid; 205 | this.setUserSelectionByRange( 206 | this.mapHighlightIdToRange[this.selectedHighlightId] 207 | ); 208 | } 209 | }, 20); 210 | }, 211 | onHighlightHoverStateChange: (context, element, hovering) => { 212 | if (hovering) { 213 | element.style.backgroundColor = "#EEEEEE"; 214 | } else { 215 | context.marker.highlightPainter.paintHighlight(context, element); 216 | } 217 | }, 218 | }, 219 | highlightPainter: { 220 | paintHighlight: (context, element) => { 221 | element.style.textDecoration = "underline"; 222 | element.style.textDecorationColor = "#f6b80b"; 223 | if (context.serializedRange.annotation) { 224 | element.style.backgroundColor = "rgba(246,184,11, 0.3)"; 225 | } else { 226 | element.style.backgroundColor = "initial"; 227 | } 228 | }, 229 | }, 230 | }); 231 | 232 | if (!demoMultiRangeMode && localStorage[LOCALSTORAGE_HIGHLIGHTS_KEY]) { 233 | this.highlights = this.loadHighlightsFromLocalStorage(); 234 | Object.keys(this.highlights).forEach((id) => { 235 | this.paint(this.highlights[id]); 236 | }); 237 | } 238 | 239 | contentWindow.document.addEventListener("mouseup", this.mouseupListener); 240 | contentWindow.addEventListener("pointermove", this.mouseMoveListener); 241 | this.marker.addEventListeners(); 242 | } 243 | } 244 | 245 | componentWillUnmount() { 246 | const contentWindow = this.iframeRef.current.contentWindow; 247 | contentWindow.document.removeEventListener("mouseup", this.mouseupListener); 248 | contentWindow.removeEventListener("pointermove", this.mouseMoveListener); 249 | this.marker.removeEventListeners(); 250 | } 251 | 252 | render() { 253 | const [top, left, width] = findProperTopLeftAndWidth( 254 | (this.state.userSelection || {}).clientRect 255 | ); 256 | return ( 257 |
258 | {!!this.state.userSelection && 259 | !this.state.hideHighlightButtons && 260 | ReactDOM.createPortal( 261 |
269 |
270 | {this.renderHighlightButtons()} 271 |
272 |
, 273 | document.body 274 | )} 275 | 276 |
277 |
278 | Here is a random number: {Math.random()}, try highlighting around 279 | it. 280 |
281 | 282 | abc 283 | 284 | blacklisted 285 | 286 | vcxdsf 287 | qwerthgf 288 | 289 |
290 | 000000000000000000000000000000000000000000000000 291 | 292 | visibility hidden !!{" "} 293 | test 294 | 295 | 111111111111111111111111111111111111111111111111 296 |
297 |
298 |