Merge branch 'drawio-bower' into 5.4-rc
|
@ -0,0 +1,12 @@
|
|||
# EditorConfig is awesome: https://EditorConfig.org
|
||||
|
||||
# top-most EditorConfig file
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
charset = utf-8
|
|
@ -1,5 +1,5 @@
|
|||
[ignore]
|
||||
.*/bower_components/.*
|
||||
.*/components/.*
|
||||
.*/node_modules/lesshint/*
|
||||
[include]
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
node_modules/
|
||||
www/components/
|
||||
www/bower_components/
|
||||
www/common/onlyoffice/sdkjs
|
||||
www/common/onlyoffice/web-apps
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
# 5.4.0
|
||||
|
||||
## Update notes
|
||||
|
||||
TODO add the up to date diagram hashes to the nginx config. See example.nginx.conf and https://github.com/cryptpad/cryptpad/commit/00af2c3efb4c155a7a793377aeddcc246d0b1aa2
|
||||
|
||||
# 5.3.0
|
||||
|
||||
## Goals
|
||||
|
|
After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
|
@ -6,8 +6,8 @@
|
|||
viewBox="0 0 135.46606 135.46728"
|
||||
id="svg942"
|
||||
sodipodi:docname="favicon_source.svg"
|
||||
inkscape:version="1.1.1 (1:1.1+202109281949+c3084ef5ed)"
|
||||
inkscape:export-filename="/home/david/cryptpad/customize.dist/favicon/alt-favicon-document.png"
|
||||
inkscape:version="1.2.2 (1:1.2.2+202305151914+b0a8486541)"
|
||||
inkscape:export-filename="alt-favicon-diagram.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
|
@ -32,6 +32,14 @@
|
|||
</metadata>
|
||||
<defs
|
||||
id="defs946">
|
||||
<linearGradient
|
||||
id="linearGradient1098"
|
||||
inkscape:swatch="solid">
|
||||
<stop
|
||||
style="stop-color:#ce3ad3;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop1096" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient943"
|
||||
inkscape:swatch="solid">
|
||||
|
@ -81,6 +89,15 @@
|
|||
x2="458.45312"
|
||||
y2="256.63477"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#linearGradient1098"
|
||||
id="linearGradient1100"
|
||||
x1="53.544922"
|
||||
y1="256.63476"
|
||||
x2="458.45312"
|
||||
y2="256.63476"
|
||||
gradientUnits="userSpaceOnUse" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
|
@ -92,18 +109,20 @@
|
|||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1832"
|
||||
inkscape:window-height="1133"
|
||||
inkscape:window-height="1128"
|
||||
id="namedview944"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.819555"
|
||||
inkscape:cx="60.398631"
|
||||
inkscape:cy="380.6944"
|
||||
inkscape:window-x="26"
|
||||
inkscape:window-y="23"
|
||||
inkscape:cx="12.811831"
|
||||
inkscape:cy="384.96501"
|
||||
inkscape:window-x="1280"
|
||||
inkscape:window-y="745"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g952"
|
||||
inkscape:current-layer="g1160"
|
||||
inkscape:document-rotation="0"
|
||||
inkscape:pagecheckerboard="0" />
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
|
@ -270,7 +289,7 @@
|
|||
inkscape:groupmode="layer"
|
||||
id="g373"
|
||||
inkscape:label="[export] shield [OO_doc]"
|
||||
style="display:inline">
|
||||
style="display:none">
|
||||
<g
|
||||
id="g371"
|
||||
transform="matrix(1.4853714,0,0,1.4853714,12.798765,-0.61151946)">
|
||||
|
@ -593,24 +612,62 @@
|
|||
style="fill:#2c9e98;fill-opacity:1">
|
||||
<path
|
||||
d="m 128.98,30.355 0.55499,39.644 h 33.141 l 0.004,-39.644 z"
|
||||
style="fill:#a72ba7;fill-opacity:0.4"
|
||||
style="fill:#8f40f5;fill-opacity:0.4"
|
||||
id="path1009" />
|
||||
<path
|
||||
d="m 162.69,70 0.003,43.946 c 12.825,-5.8796 32.762,-17.077 33.127,-43.157 l 0.0108,-0.78911 z"
|
||||
style="fill:#a72ba7;fill-opacity:0.4"
|
||||
style="fill:#8f40f5;fill-opacity:0.4"
|
||||
id="path1011" />
|
||||
<path
|
||||
id="path1013"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#a72ba7;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:29.7103;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient1100);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:29.7103;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 53.544922,4.7617188 0.185547,15.0449222 3.179687,256.695309 c 0.953422,76.9836 32.1134,127.84094 71.929684,161.46875 39.81629,33.62784 87.45761,51.28822 122.63086,67.4668 l 6.66602,3.07031 6.48047,-3.43359 c 31.31498,-16.53917 79.28982,-34.11259 120.05273,-67.35547 40.76297,-33.24293 73.7832,-83.90225 73.7832,-161.40234 V 130.05664 L 323.60742,4.7617188 Z M 83.621094,34.476562 H 291.42578 V 160.75781 H 428.75 v 115.5586 c 0,69.39522 -27.09036,109.2031 -62.86133,138.375 -33.66112,27.45122 -74.94064,43.84038 -108.41015,60.82226 C 221.97696,459.48582 180.81444,442.98268 148.00586,415.27344 113.30228,385.96366 87.475071,345.55977 86.615234,276.13086 Z m 237.519526,8.556641 94.7461,88.021487 h -94.7461 z"
|
||||
transform="matrix(0.17812685,0,0,0.17812685,116.76305,26.860695)" />
|
||||
<g
|
||||
transform="matrix(1.1107,0,0,1.1107,18.926,21.932)"
|
||||
style="fill:#a72ba7;fill-opacity:1"
|
||||
style="fill:#8f40f5;fill-opacity:1"
|
||||
id="g1017">
|
||||
<path
|
||||
id="path1015"
|
||||
style="fill:#a72ba7;fill-opacity:1;stroke-width:6.23544"
|
||||
style="fill:#8f40f5;fill-opacity:1;stroke-width:6.23544"
|
||||
d="m 255.8125,188.08008 a 50.622452,50.622452 0 0 0 -50.62305,50.62304 50.622452,50.622452 0 0 0 28.73243,45.64258 L 216.25,377.66602 h 79.14648 l -17.67382,-93.33008 a 50.622452,50.622452 0 0 0 28.71289,-45.63282 50.622452,50.622452 0 0 0 -50.62305,-50.62304 z"
|
||||
transform="matrix(0.1603735,0,0,0.1603735,88.085934,4.437467)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="g1160"
|
||||
inkscape:label="[export] shield [diagram]"
|
||||
style="display:inline">
|
||||
<g
|
||||
id="g1158"
|
||||
transform="matrix(1.4853714,0,0,1.4853714,12.798765,-0.61151946)">
|
||||
<g
|
||||
transform="translate(-125.38,-26.449)"
|
||||
id="g1156"
|
||||
style="fill:#2c9e98;fill-opacity:1">
|
||||
<path
|
||||
d="m 128.98,30.355 0.55499,39.644 h 33.141 l 0.004,-39.644 z"
|
||||
style="fill:#ce3ad3;fill-opacity:0.40000001"
|
||||
id="path1146" />
|
||||
<path
|
||||
d="m 162.69,70 0.003,43.946 c 12.825,-5.8796 32.762,-17.077 33.127,-43.157 l 0.0108,-0.78911 z"
|
||||
style="fill:#ce3ad3;fill-opacity:0.40000001"
|
||||
id="path1148" />
|
||||
<path
|
||||
id="path1150"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient1100);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:29.7103;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 53.544922,4.7617188 0.185547,15.0449222 3.179687,256.695309 c 0.953422,76.9836 32.1134,127.84094 71.929684,161.46875 39.81629,33.62784 87.45761,51.28822 122.63086,67.4668 l 6.66602,3.07031 6.48047,-3.43359 c 31.31498,-16.53917 79.28982,-34.11259 120.05273,-67.35547 40.76297,-33.24293 73.7832,-83.90225 73.7832,-161.40234 V 130.05664 L 323.60742,4.7617188 Z M 83.621094,34.476562 H 291.42578 V 160.75781 H 428.75 v 115.5586 c 0,69.39522 -27.09036,109.2031 -62.86133,138.375 -33.66112,27.45122 -74.94064,43.84038 -108.41015,60.82226 C 221.97696,459.48582 180.81444,442.98268 148.00586,415.27344 113.30228,385.96366 87.475071,345.55977 86.615234,276.13086 Z m 237.519526,8.556641 94.7461,88.021487 h -94.7461 z"
|
||||
transform="matrix(0.17812685,0,0,0.17812685,116.76305,26.860695)" />
|
||||
<g
|
||||
transform="matrix(1.1107,0,0,1.1107,18.926,21.932)"
|
||||
style="fill:#8f40f5;fill-opacity:1"
|
||||
id="g1154">
|
||||
<path
|
||||
id="path1152"
|
||||
style="fill:#ce3ad3;fill-opacity:1;stroke-width:6.23544"
|
||||
d="m 255.8125,188.08008 a 50.622452,50.622452 0 0 0 -50.62305,50.62304 50.622452,50.622452 0 0 0 28.73243,45.64258 L 216.25,377.66602 h 79.14648 l -17.67382,-93.33008 a 50.622452,50.622452 0 0 0 28.71289,-45.63282 50.622452,50.622452 0 0 0 -50.62305,-50.62304 z"
|
||||
transform="matrix(0.1603735,0,0,0.1603735,88.085934,4.437467)" />
|
||||
</g>
|
||||
|
|
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 44 KiB |
After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
|
@ -40,4 +40,5 @@
|
|||
<glyph unicode="" glyph-name="form-poll-maybe" horiz-adv-x="1094" d="M856.492 569.333c0 9.987-3.996 19.973-11.187 27.163l-54.325 54.327c-7.191 7.191-17.175 11.185-27.163 11.185s-19.972-3.995-27.163-11.185l-262.048-262.447-117.442 117.841c-7.191 7.19-17.175 11.185-27.163 11.185s-19.972-3.995-27.163-11.185l-54.327-54.327c-7.191-7.191-11.187-17.177-11.187-27.163s3.995-19.973 11.187-27.163l198.932-198.932c7.191-7.191 17.175-11.185 27.163-11.185s19.972 3.995 27.163 11.185l343.538 343.537c7.192 7.191 11.187 17.177 11.187 27.163zM65.903 444.364q0 120.454 35 225.454 35.454 105 101.818 184.090h73.636q-65.454-87.727-98.636-192.727-32.727-105-32.727-215.908 0-109.091 33.636-213.181t96.818-189.999h-72.727q-66.818 77.273-101.818 180.454t-35 221.818zM1027.916 444.364q0-119.545-35.454-222.727-35-103.182-101.363-179.545h-72.727q63.182 85.454 96.818 189.545 33.636 104.545 33.636 213.636 0 110.909-33.182 215.908-32.727 105-98.182 192.727h73.636q66.818-79.545 101.818-184.999 35-105 35-224.545z" />
|
||||
<glyph unicode="" glyph-name="destroy" horiz-adv-x="1094" d="M191.671 946.511c-28.024 0-50.742-22.731-50.742-50.756v-399.384h-74.822c-29.561-0.003-53.524-23.966-53.527-53.527 0.025-29.545 23.982-53.484 53.527-53.487h174.773l-88.853-95.929 95.929-104.66-95.835-104.524 121.3-132.182 43.855 55.423-70.369 76.813 95.781 104.471-95.687 104.364 89.122 96.225h190.445l-88.853-95.929 95.929-104.66-95.781-104.524 121.246-132.182 43.855 55.423-70.369 76.813 95.781 104.471-95.687 104.364 89.122 96.225h190.445l-88.853-95.929 95.983-104.66-95.835-104.524 121.286-132.182 43.815 55.423-70.383 76.759 95.835 104.524-95.727 104.417 89.122 96.171h117.318c29.55-0.004 53.515 23.936 53.54 53.487-0.003 29.566-23.974 53.531-53.54 53.527h-71.728v162.501c0 28.024-16.394 67.15-35.958 86.714l-164.966 164.966c-19.564 19.564-58.69 35.958-86.714 35.958zM208.607 878.832h406.073v-219.96c0-28.024 22.745-50.769 50.769-50.769h219.96v-111.732h-676.802zM682.359 874.608c8.989-3.172 17.984-7.936 21.685-11.636l165.505-165.505c3.702-3.7 8.463-12.696 11.636-21.685h-198.826z" />
|
||||
<glyph unicode="" glyph-name="drive" horiz-adv-x="878" d="M586.884 124.372c0 23.946-19.592 43.537-43.537 43.537s-43.537-19.592-43.537-43.537c0-23.946 19.592-43.537 43.537-43.537s43.537 19.592 43.537 43.537zM726.203 124.372c0 23.946-19.592 43.537-43.537 43.537s-43.537-19.592-43.537-43.537c0-23.946 19.592-43.537 43.537-43.537s43.537 19.592 43.537 43.537zM787.155 37.298c0-9.252-8.163-17.415-17.415-17.415h-661.767c-9.252 0-17.415 8.163-17.415 17.415v174.149c0 9.252 8.163 17.415 17.415 17.415h661.767c9.252 0 17.415-8.163 17.415-17.415zM117.77 298.521l85.442 262.312c2.721 9.252 13.061 16.326 22.857 16.326h425.577c9.796 0 20.136-7.075 22.857-16.326l85.442-262.312zM856.815 211.447c0 14.694-4.354 27.211-8.707 40.816l-107.211 329.795c-12.517 38.095-48.979 64.762-89.251 64.762h-425.577c-40.272 0-76.734-26.667-89.251-64.762l-107.211-329.795c-4.354-13.605-8.707-26.122-8.707-40.816v-174.149c0-47.891 39.184-87.075 87.075-87.075h661.767c47.891 0 87.075 39.184 87.075 87.075z" />
|
||||
<glyph unicode="" glyph-name="diagram" horiz-adv-x="878" d="M827.746 734.667l-173.333 173.333c-20.556 20.556-61.667 37.778-91.111 37.778h-497.778c-29.444 0-53.333-23.889-53.333-53.333v-888.889c0-29.444 23.889-53.333 53.333-53.333h746.667c29.444 0 53.333 23.889 53.333 53.333v640c0 29.444-17.222 70.556-37.778 91.111zM581.079 870.222c9.444-3.333 18.889-8.333 22.778-12.222l173.889-173.889c3.889-3.889 8.889-13.333 12.222-22.778h-208.889zM794.413 21.333h-711.111v853.333h426.667v-231.111c0-29.444 23.889-53.333 53.333-53.333h231.111zM142.19 699.381v-233.667h242.476l103.19-129.667-66.571 7.571c-0.464 0.060-1 0.095-1.544 0.095-6.469 0-11.802-4.862-12.545-11.13l-0.006-0.060c-0.043-0.393-0.068-0.848-0.068-1.309 0-6.459 4.847-11.786 11.103-12.542l0.060-0.006 119.095-13.714 13.667 119.095c0.059 0.459 0.093 0.99 0.093 1.529 0 6.519-4.937 11.884-11.276 12.562l-0.055 0.005c-0.401 0.045-0.865 0.071-1.336 0.071-6.528 0-11.899-4.95-12.564-11.302l-0.005-0.055-7.571-66.571-101.952 128.143v210.952h-274.19zM190.952 650.619h176.667v-136.143h-176.667v136.143zM628.143 307.952c-64.030 0-117.525-47.559-126.857-109.095l-73.095 0.762 47.857 46.81c2.398 2.303 3.888 5.536 3.888 9.117 0 3.487-1.413 6.645-3.698 8.931v0c-2.288 2.295-5.452 3.714-8.947 3.714-0.018 0-0.037 0-0.055 0h0.003c-0.058 0.001-0.127 0.002-0.196 0.002-3.425 0-6.532-1.363-8.807-3.576l0.003 0.003-85.524-83.81 83.809-85.714c2.293-2.327 5.478-3.768 9.001-3.768 3.427 0 6.536 1.365 8.812 3.58l-0.003-0.003c2.327 2.293 3.768 5.478 3.768 9.001 0 3.427-1.365 6.536-3.58 8.812l0.003-0.003-46.952 47.857 73.81-0.714c9.614-61.186 62.962-108.286 126.762-108.286 70.545 0 128.095 57.551 128.095 128.095s-57.551 128.286-128.095 128.286zM626.095 259.19c0.686 0.017 1.357 0 2.048 0 44.192 0 79.333-35.332 79.333-79.524s-35.141-79.524-79.333-79.524c-44.192 0-79.524 35.332-79.524 79.524 0 43.501 34.254 78.443 77.476 79.524zM244.048 438.143l-85.381-84.048c-2.384-2.301-3.864-5.525-3.864-9.094 0-3.475 1.403-6.623 3.674-8.907l-0.001 0.001c2.295-2.341 5.49-3.792 9.024-3.792s6.729 1.451 9.022 3.79l0.002 0.002 47.571 46.762-0.952-117.667h-80.952v-191.286h202.381v191.286h-82.381l0.905 117.571 47.143-47.81c2.29-2.312 5.466-3.744 8.977-3.744 3.439 0 6.557 1.374 8.835 3.603l-0.002-0.002c2.384 2.301 3.864 5.525 3.864 9.094 0 3.475-1.403 6.623-3.674 8.907l0.001-0.001-84.19 85.333zM190.952 216.429h104.857v-93.762h-104.857v93.762z" />
|
||||
</font></defs></svg>
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 44 KiB |
|
@ -1,10 +1,10 @@
|
|||
@font-face {
|
||||
font-family: 'cptools';
|
||||
src: url('fonts/cptools.eot?pmxg4b');
|
||||
src: url('fonts/cptools.eot?pmxg4b#iefix') format('embedded-opentype'),
|
||||
url('fonts/cptools.ttf?pmxg4b') format('truetype'),
|
||||
url('fonts/cptools.woff?pmxg4b') format('woff'),
|
||||
url('fonts/cptools.svg?pmxg4b#cptools') format('svg');
|
||||
src: url('fonts/cptools.eot?nobkj');
|
||||
src: url('fonts/cptools.eot?nobkj#iefix') format('embedded-opentype'),
|
||||
url('fonts/cptools.ttf?nobkj') format('truetype'),
|
||||
url('fonts/cptools.woff?nobkj') format('woff'),
|
||||
url('fonts/cptools.svg?nobkj#cptools') format('svg');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
font-display: block;
|
||||
|
@ -26,6 +26,9 @@
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.cptools-diagram:before {
|
||||
content: "\e921";
|
||||
}
|
||||
.cptools-drive:before {
|
||||
content: "\e920";
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 17 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 16 KiB |
|
@ -105,6 +105,8 @@ define(req, function(AppConfig, Default, Language) {
|
|||
extend(messages, Language);
|
||||
}
|
||||
|
||||
messages.type.diagram = "Diagram"; // XXX
|
||||
|
||||
messages._languages = map;
|
||||
messages._languageUsed = language;
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ define([
|
|||
[ 'kanban', Msg.type.kanban],
|
||||
[ 'code', Msg.type.code],
|
||||
[ 'form', Msg.type.form],
|
||||
[ 'whiteboard', Msg.type.whiteboard],
|
||||
[ 'diagram', Msg.type.diagram],
|
||||
[ 'slide', Msg.type.slide]
|
||||
].filter(function (x) {
|
||||
return isAvailableType(x[0]);
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
slide: #e57614;
|
||||
poll: #2c9e98;
|
||||
form: #2c9e98;
|
||||
whiteboard: #a72ba7;
|
||||
whiteboard: #8f40f5;
|
||||
diagram: #ce3ad3;
|
||||
kanban: #8C4;
|
||||
sheet: #40865c;
|
||||
doc: #5170B5;
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
slide: #e57614;
|
||||
poll: #2c9e98;
|
||||
form: #2c9e98;
|
||||
whiteboard: #a72ba7;
|
||||
whiteboard: #8f40f5;
|
||||
diagram: #ce3ad3;
|
||||
kanban: #8C4;
|
||||
sheet: #40865c;
|
||||
doc: #5170B5;
|
||||
|
|
|
@ -161,6 +161,11 @@ server {
|
|||
# We've applied other sandboxing techniques to mitigate the risk of running WebAssembly in this privileged scope
|
||||
if ($uri ~ ^\/unsafeiframe\/inner\.html.*$) { set $unsafe 1; }
|
||||
|
||||
# draw.io uses inline script tags in it's index.html. The hashes are added here.
|
||||
if ($uri ~ ^\/components\/drawio\/src\/main\/webapp\/index.html.*$) {
|
||||
set $scriptSrc "'self' 'sha256-6zAB96lsBZREqf0sT44BhH1T69sm7HrN34rpMOcWbNo=' 'sha256-6g514VrT/cZFZltSaKxIVNFF46+MFaTSDTPB8WfYK+c=' resource: https://${main_domain}";
|
||||
}
|
||||
|
||||
# privileged contexts allow a few more rights than unprivileged contexts, though limits are still applied
|
||||
if ($unsafe) {
|
||||
set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: https://${main_domain}";
|
||||
|
|
|
@ -26,7 +26,7 @@ Default.commonCSP = function (Env) {
|
|||
if you are deploying to production, you'll probably want to remove
|
||||
the ws://* directive
|
||||
*/
|
||||
"connect-src 'self' blob: " + (/^https:/.test(domain)? 'wss:': domain.replace('http://', 'ws://')) + ' ' + domain + sandbox + accounts_api,
|
||||
"connect-src 'self' localhost blob: " + (/^https:/.test(domain)? 'wss:': domain.replace('http://', 'ws://')) + ' ' + domain + sandbox + accounts_api,
|
||||
|
||||
// data: is used by codemirror
|
||||
"img-src 'self' data: blob:" + domain,
|
||||
|
@ -47,6 +47,10 @@ Default.padContentSecurity = function (Env) {
|
|||
return (Default.commonCSP(Env).join('; ') + "script-src 'self' 'unsafe-eval' 'unsafe-inline' resource: " + Env.httpUnsafeOrigin).replace(/\s+/g, ' ');
|
||||
};
|
||||
|
||||
Default.diagramContentSecurity = function (Env) {
|
||||
return (Default.commonCSP(Env).join('; ') + "script-src 'self' 'sha256-6zAB96lsBZREqf0sT44BhH1T69sm7HrN34rpMOcWbNo=' 'sha256-6g514VrT/cZFZltSaKxIVNFF46+MFaTSDTPB8WfYK+c=' resource: " + Env.httpUnsafeOrigin).replace(/\s+/g, ' ');
|
||||
};
|
||||
|
||||
Default.httpHeaders = function (Env) {
|
||||
return {
|
||||
"X-XSS-Protection": "1; mode=block",
|
||||
|
|
|
@ -220,6 +220,8 @@ module.exports.create = function (config) {
|
|||
|
||||
curvePrivate: curve.secretKey,
|
||||
curvePublic: Nacl.util.encodeBase64(curve.publicKey),
|
||||
|
||||
selfDestructTo: {},
|
||||
};
|
||||
|
||||
(function () {
|
||||
|
|
|
@ -63,6 +63,16 @@ module.exports.create = function (Env, cb) {
|
|||
error: err,
|
||||
});
|
||||
}
|
||||
|
||||
if (metadata && metadata.selfdestruct && metadata.selfdestruct !== Env.id) {
|
||||
HK.expireChannel(Env, channelName);
|
||||
return void cb('ESELFDESTRUCT');
|
||||
}
|
||||
|
||||
if (Env.selfDestructTo && Env.selfDestructTo[channelName]) {
|
||||
clearTimeout(Env.selfDestructTo[channelName]);
|
||||
}
|
||||
|
||||
if (!metadata || (metadata && !metadata.restricted)) {
|
||||
// the channel doesn't have metadata, or it does and it's not restricted
|
||||
// either way, let them join.
|
||||
|
|
|
@ -120,7 +120,7 @@ var CHECKPOINT_PATTERN = /^cp\|(([A-Za-z0-9+\/=]+)\|)?/;
|
|||
/* expireChannel is here to clean up channels that should have been removed
|
||||
but for some reason are still present
|
||||
*/
|
||||
const expireChannel = function (Env, channel) {
|
||||
const expireChannel = HK.expireChannel = function (Env, channel) {
|
||||
return void Env.store.archiveChannel(channel, function (err) {
|
||||
Env.Log.info("ARCHIVAL_CHANNEL_BY_HISTORY_KEEPER_EXPIRATION", {
|
||||
channelId: channel,
|
||||
|
@ -133,8 +133,14 @@ const expireChannel = function (Env, channel) {
|
|||
* cleans up memory structures which are managed entirely by the historyKeeper
|
||||
*/
|
||||
const dropChannel = HK.dropChannel = function (Env, chanName) {
|
||||
let meta = Env.metadata_cache[chanName];
|
||||
delete Env.metadata_cache[chanName];
|
||||
delete Env.channel_cache[chanName];
|
||||
if (meta && meta.selfdestruct && Env.selfDestructTo) {
|
||||
Env.selfDestructTo[chanName] = setTimeout(function () {
|
||||
expireChannel(Env, chanName); // XXX add new function?
|
||||
}, 30*1000); // XXX CONSTANT XXX XXX XXX
|
||||
}
|
||||
};
|
||||
|
||||
/* checkExpired
|
||||
|
@ -565,6 +571,10 @@ const handleRPC = function (Env, Server, seq, userId, parsed) {
|
|||
if the provided metadata has an expire time then we also create a task to expire it.
|
||||
*/
|
||||
const handleFirstMessage = function (Env, channelName, metadata) {
|
||||
if (metadata.selfdestruct) {
|
||||
// Set the selfdestruct flag to history keeper ID to handle server crash.
|
||||
metadata.selfdestruct = Env.id;
|
||||
}
|
||||
Env.store.writeMetadata(channelName, JSON.stringify(metadata), function (err) {
|
||||
if (err) {
|
||||
// FIXME tell the user that there was a channel error?
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
"packages": {
|
||||
"": {
|
||||
"name": "cryptpad",
|
||||
"version": "5.3.0",
|
||||
"version": "5.4.0",
|
||||
"license": "AGPL-3.0+",
|
||||
"dependencies": {
|
||||
"@mcrowe/minibloom": "^0.2.0",
|
||||
|
@ -18,11 +18,12 @@
|
|||
"chainpad-listmap": "^1.0.0",
|
||||
"chainpad-netflux": "^1.0.0",
|
||||
"chainpad-server": "^5.1.0",
|
||||
"ckeditor": "^4.12.1",
|
||||
"ckeditor": "npm:ckeditor4@^4.14.1",
|
||||
"codemirror": "^5.19.0",
|
||||
"components-font-awesome": "^4.6.3",
|
||||
"croppie": "^2.5.0",
|
||||
"dragula": "3.7.2",
|
||||
"drawio": "cryptpad/drawio-npm#npm",
|
||||
"express": "~4.18.2",
|
||||
"file-saver": "1.3.1",
|
||||
"fs-extra": "^7.0.0",
|
||||
|
@ -38,6 +39,7 @@
|
|||
"netflux-websocket": "^1.0.0",
|
||||
"nthen": "0.1.8",
|
||||
"open-sans-fontface": "^1.4.0",
|
||||
"pako": "^2.1.0",
|
||||
"prompt-confirm": "^2.0.4",
|
||||
"pull-stream": "^3.6.1",
|
||||
"require-css": "0.1.10",
|
||||
|
@ -50,7 +52,8 @@
|
|||
"stream-to-pull-stream": "^1.7.2",
|
||||
"tweetnacl": "~0.12.2",
|
||||
"ulimit": "0.0.2",
|
||||
"ws": "^3.3.1"
|
||||
"ws": "^3.3.1",
|
||||
"x2js": "^3.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jshint": "^2.13.4",
|
||||
|
@ -110,6 +113,14 @@
|
|||
"integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@xmldom/xmldom": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.8.tgz",
|
||||
"integrity": "sha512-0LNz4EY8B/8xXY86wMrQ4tz6zEHZv9ehFMJPm8u2gq5lQ71cfRKdaKyxfJAx5aUoyzx0qzgURblTisPGgz3d+Q==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
|
@ -929,10 +940,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/ckeditor": {
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmjs.org/ckeditor/-/ckeditor-4.12.1.tgz",
|
||||
"integrity": "sha512-pH2Su4oi0D4iN/3U8nUcwI7/lXHoOJi0aiN8e2zxnm4Ow5kq8eZP2ZGmpYyuqRyKZ2tHaU8+OyYi7laXcjiq9Q==",
|
||||
"deprecated": "We have renamed the @ckeditor package. New versions are available under the @ckeditor4 name."
|
||||
"name": "ckeditor4",
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ckeditor4/-/ckeditor4-4.21.0.tgz",
|
||||
"integrity": "sha512-OAMw68puJcrKFtsPZwIWVB/upYLgJpFw1yTuBBIhoreY+g/f0SttjQY0I/fUwxevVUHvgmRVNeJwNl8qkJPvyw=="
|
||||
},
|
||||
"node_modules/class-utils": {
|
||||
"version": "0.3.6",
|
||||
|
@ -1395,6 +1406,11 @@
|
|||
"crossvent": "1.5.4"
|
||||
}
|
||||
},
|
||||
"node_modules/drawio": {
|
||||
"name": "drawio-cp-npm",
|
||||
"version": "21.5.2",
|
||||
"resolved": "git+ssh://git@github.com/cryptpad/drawio-npm.git#c49df7017c6dc6cc55a18768b4fe4e3e030f8597"
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
@ -2430,6 +2446,11 @@
|
|||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"node_modules/jszip/node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"node_modules/jszip/node_modules/readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
|
@ -3019,9 +3040,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
||||
},
|
||||
"node_modules/parse-json": {
|
||||
"version": "4.0.0",
|
||||
|
@ -4486,6 +4507,14 @@
|
|||
"ultron": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/x2js": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/x2js/-/x2js-3.4.4.tgz",
|
||||
"integrity": "sha512-yG/ThaBCgnsa3aoMPAe7QwDpcyU4D70hjXC4Y1lZSfD/Tgd0MpE19FnZZRAjekryw0c8cffpOt9zsPEiqktO6Q==",
|
||||
"dependencies": {
|
||||
"@xmldom/xmldom": "^0.8.3"
|
||||
}
|
||||
},
|
||||
"node_modules/xmhell": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmhell/-/xmhell-0.1.2.tgz",
|
||||
|
@ -4539,6 +4568,11 @@
|
|||
"integrity": "sha512-miWq2m2FiQZmaHfdZNcbpp9PuXg34W5JZ5CrJ/BaS70VuhoJENBEQybeiYSaPBRNq6KQGnjfEnc/F3PN++D+XQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@xmldom/xmldom": {
|
||||
"version": "0.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.8.tgz",
|
||||
"integrity": "sha512-0LNz4EY8B/8xXY86wMrQ4tz6zEHZv9ehFMJPm8u2gq5lQ71cfRKdaKyxfJAx5aUoyzx0qzgURblTisPGgz3d+Q=="
|
||||
},
|
||||
"accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
|
@ -5165,9 +5199,9 @@
|
|||
}
|
||||
},
|
||||
"ckeditor": {
|
||||
"version": "4.12.1",
|
||||
"resolved": "https://registry.npmjs.org/ckeditor/-/ckeditor-4.12.1.tgz",
|
||||
"integrity": "sha512-pH2Su4oi0D4iN/3U8nUcwI7/lXHoOJi0aiN8e2zxnm4Ow5kq8eZP2ZGmpYyuqRyKZ2tHaU8+OyYi7laXcjiq9Q=="
|
||||
"version": "npm:ckeditor4@4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/ckeditor4/-/ckeditor4-4.21.0.tgz",
|
||||
"integrity": "sha512-OAMw68puJcrKFtsPZwIWVB/upYLgJpFw1yTuBBIhoreY+g/f0SttjQY0I/fUwxevVUHvgmRVNeJwNl8qkJPvyw=="
|
||||
},
|
||||
"class-utils": {
|
||||
"version": "0.3.6",
|
||||
|
@ -5540,6 +5574,10 @@
|
|||
"crossvent": "1.5.4"
|
||||
}
|
||||
},
|
||||
"drawio": {
|
||||
"version": "git+ssh://git@github.com/cryptpad/drawio-npm.git#c49df7017c6dc6cc55a18768b4fe4e3e030f8597",
|
||||
"from": "drawio@cryptpad/drawio-npm#npm"
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
@ -6359,6 +6397,11 @@
|
|||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
|
||||
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
|
||||
},
|
||||
"pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.3.7",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
|
||||
|
@ -6836,9 +6879,9 @@
|
|||
}
|
||||
},
|
||||
"pako": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
|
||||
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
|
||||
"integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
|
||||
},
|
||||
"parse-json": {
|
||||
"version": "4.0.0",
|
||||
|
@ -8002,6 +8045,14 @@
|
|||
"ultron": "~1.1.0"
|
||||
}
|
||||
},
|
||||
"x2js": {
|
||||
"version": "3.4.4",
|
||||
"resolved": "https://registry.npmjs.org/x2js/-/x2js-3.4.4.tgz",
|
||||
"integrity": "sha512-yG/ThaBCgnsa3aoMPAe7QwDpcyU4D70hjXC4Y1lZSfD/Tgd0MpE19FnZZRAjekryw0c8cffpOt9zsPEiqktO6Q==",
|
||||
"requires": {
|
||||
"@xmldom/xmldom": "^0.8.3"
|
||||
}
|
||||
},
|
||||
"xmhell": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/xmhell/-/xmhell-0.1.2.tgz",
|
||||
|
|
|
@ -35,7 +35,7 @@
|
|||
"chainpad": "^5.2.6",
|
||||
"chainpad-listmap": "^1.0.0",
|
||||
"chainpad-netflux": "^1.0.0",
|
||||
"ckeditor": "^4.12.1",
|
||||
"ckeditor": "npm:ckeditor4@^4.14.1",
|
||||
"codemirror": "^5.19.0",
|
||||
"components-font-awesome": "^4.6.3",
|
||||
"croppie": "^2.5.0",
|
||||
|
@ -54,7 +54,10 @@
|
|||
"requirejs": "2.3.5",
|
||||
"requirejs-plugins": "^1.0.2",
|
||||
"scrypt-async": "1.2.0",
|
||||
"sortablejs": "^1.6.0"
|
||||
"sortablejs": "^1.6.0",
|
||||
"drawio": "cryptpad/drawio-npm#npm",
|
||||
"pako": "^2.1.0",
|
||||
"x2js": "^3.4.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jshint": "^2.13.4",
|
||||
|
|
|
@ -136,6 +136,7 @@ var appIndexesToBuild = [
|
|||
'form',
|
||||
'poll',
|
||||
'whiteboard',
|
||||
'diagram',
|
||||
'slide',
|
||||
'file',
|
||||
'calendar',
|
||||
|
|
|
@ -40,6 +40,9 @@ Fse.rmSync(oldComponentsPath, { recursive: true, force: true });
|
|||
"saferphore",
|
||||
"nthen",
|
||||
"netflux-websocket",
|
||||
"drawio",
|
||||
"pako",
|
||||
"x2js"
|
||||
].forEach(l => {
|
||||
const source = Path.join("node_modules", l);
|
||||
const destination = Path.join(componentsPath, l);
|
||||
|
|
18
server.js
|
@ -61,9 +61,15 @@ var getHeaders = function (Env, type) {
|
|||
headers = Default.httpHeaders(Env);
|
||||
}
|
||||
|
||||
headers['Content-Security-Policy'] = type === 'office'?
|
||||
Default.padContentSecurity(Env):
|
||||
Default.contentSecurity(Env);
|
||||
var csp;
|
||||
if (type === 'office') {
|
||||
csp = Default.padContentSecurity(Env);
|
||||
} else if (type === 'diagram') {
|
||||
csp = Default.diagramContentSecurity(Env);
|
||||
} else {
|
||||
csp = Default.contentSecurity(Env);
|
||||
}
|
||||
headers['Content-Security-Policy'] = csp;
|
||||
|
||||
if (Env.NO_SANDBOX) { // handles correct configuration for local development
|
||||
// https://stackoverflow.com/questions/11531121/add-duplicate-http-response-headers-in-nodejs
|
||||
|
@ -90,6 +96,8 @@ var setHeaders = function (req, res) {
|
|||
type = 'office';
|
||||
} else if (/^\/api\/(broadcast|config)/.test(req.url)) {
|
||||
type = 'api';
|
||||
} else if (/^\/components\/drawio\/src\/main\/webapp\/index.html.*$/.test(req.url)) {
|
||||
type = 'diagram';
|
||||
} else {
|
||||
type = 'standard';
|
||||
}
|
||||
|
@ -164,6 +172,10 @@ app.get(mainPagePattern, Express.static(Path.resolve('customize.dist')));
|
|||
app.use("/blob", Express.static(Env.paths.blob, {
|
||||
maxAge: Env.DEV_MODE? "0d": "365d"
|
||||
}));
|
||||
|
||||
app.head("/datastore", Express.static(Env.paths.data, {
|
||||
maxAge: "0d"
|
||||
}));
|
||||
app.use("/datastore", Express.static(Env.paths.data, {
|
||||
maxAge: "0d"
|
||||
}));
|
||||
|
|
|
@ -560,7 +560,7 @@ define([
|
|||
I used 'apply' with 'arguments' to avoid breaking things if this API ever changes.
|
||||
*/
|
||||
var ret = CodeMirror.fileImporter.apply(null, Array.prototype.slice.call(arguments));
|
||||
previewPane.modeChange(ret.mode);
|
||||
previewPane.modeChange(ret.highlightMode);
|
||||
return ret;
|
||||
});
|
||||
|
||||
|
|
|
@ -12,7 +12,8 @@ define(function() {
|
|||
* You should never remove the drive from this list.
|
||||
*/
|
||||
AppConfig.availablePadTypes = ['drive', 'teams', 'sheet', 'doc', 'presentation', 'pad', 'kanban', 'code', 'form', 'poll', 'whiteboard',
|
||||
'file', 'contacts', 'slide', 'convert'];
|
||||
'file', 'contacts', 'slide', 'convert', 'diagram'];
|
||||
|
||||
/* The registered only types are apps restricted to registered users.
|
||||
* You should never remove apps from this list unless you know what you're doing. The apps
|
||||
* listed here by default can't work without a user account.
|
||||
|
@ -192,6 +193,7 @@ define(function() {
|
|||
poll: 'cptools-poll',
|
||||
form: 'cptools-poll',
|
||||
whiteboard: 'cptools-whiteboard',
|
||||
diagram: 'cptools-diagram',
|
||||
todo: 'cptools-todo',
|
||||
contacts: 'fa-address-book',
|
||||
calendar: 'fa-calendar',
|
||||
|
|
|
@ -128,14 +128,8 @@ define([
|
|||
};
|
||||
|
||||
var importContent = UIElements.importContent = function (type, f, cfg) {
|
||||
return function () {
|
||||
var $files = $('<input>', {type:"file"});
|
||||
if (cfg && cfg.accept) {
|
||||
$files.attr('accept', cfg.accept);
|
||||
}
|
||||
$files.click();
|
||||
$files.on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
return function (_file) {
|
||||
var todo = function (file) {
|
||||
var reader = new FileReader();
|
||||
var parsed = file && file.name && /.+\.([^.]+)$/.exec(file.name);
|
||||
var ext = parsed && parsed[1];
|
||||
|
@ -144,7 +138,19 @@ define([
|
|||
reader.readAsArrayBuffer(file, type);
|
||||
} else {
|
||||
reader.readAsText(file, type);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (_file) { return void todo(_file); }
|
||||
|
||||
var $files = $('<input>', {type:"file"});
|
||||
if (cfg && cfg.accept) {
|
||||
$files.attr('accept', cfg.accept);
|
||||
}
|
||||
$files.click();
|
||||
$files.on('change', function (e) {
|
||||
var file = e.target.files[0];
|
||||
todo(file);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
@ -627,12 +633,16 @@ define([
|
|||
});
|
||||
|
||||
var handler = data.first? function () {
|
||||
data.first(importer);
|
||||
data.first(function () {
|
||||
importer(); // Make sure we don't pass arguments to importer
|
||||
});
|
||||
}: importer; //importContent;
|
||||
|
||||
button
|
||||
.click(common.prepareFeedback(type))
|
||||
.click(handler);
|
||||
.click(function () {
|
||||
handler();
|
||||
});
|
||||
//}
|
||||
break;
|
||||
case 'upload':
|
||||
|
|
|
@ -77,7 +77,7 @@
|
|||
},
|
||||
unreg: function (cb) {
|
||||
if (handlers.indexOf(cb) === -1) {
|
||||
return void console.error("event handler was already unregistered");
|
||||
return void console.log("event handler was already unregistered");
|
||||
}
|
||||
handlers.splice(handlers.indexOf(cb), 1);
|
||||
},
|
||||
|
|
|
@ -2529,6 +2529,7 @@ define([
|
|||
form_seed: localStorage.CP_formSeed,
|
||||
cache: rdyCfg.cache,
|
||||
noDrive: rdyCfg.noDrive,
|
||||
neverDrive: rdyCfg.neverDrive,
|
||||
disableCache: localStorage['CRYPTPAD_STORE|disableCache'],
|
||||
driveEvents: !rdyCfg.noDrive, //rdyCfg.driveEvents // Boolean
|
||||
lastVisit: Number(localStorage.lastVisit) || undefined
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
define(['/api/config'], function (ApiConfig) {
|
||||
var Module = {};
|
||||
|
||||
var apps = ['code', 'slide', 'pad', 'kanban', 'whiteboard', 'sheet', 'poll', 'teams', 'form', 'doc', 'presentation'];
|
||||
var apps = ['code', 'slide', 'pad', 'kanban', 'whiteboard', 'diagram', 'sheet', 'poll', 'teams', 'form', 'doc', 'presentation'];
|
||||
var app = window.location.pathname.slice(1, -1); // remove "/" at the beginnin and the end
|
||||
var suffix = apps.indexOf(app) !== -1 ? '-'+app : '';
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ define([
|
|||
'/common/outer/cache-store.js',
|
||||
'/common/outer/sharedfolder.js',
|
||||
'/common/outer/cursor.js',
|
||||
'/common/outer/integration.js',
|
||||
'/common/outer/onlyoffice.js',
|
||||
'/common/outer/mailbox.js',
|
||||
'/common/outer/profile.js',
|
||||
|
@ -33,7 +34,7 @@ define([
|
|||
'/components/saferphore/index.js',
|
||||
], function (ApiConfig, Sortify, UserObject, ProxyManager, Migrate, Hash, Util, Constants, Feedback,
|
||||
Realtime, Messaging, Pinpad, Cache,
|
||||
SF, Cursor, OnlyOffice, Mailbox, Profile, Team, Messenger, History,
|
||||
SF, Cursor, Integration, OnlyOffice, Mailbox, Profile, Team, Messenger, History,
|
||||
Calendar, NetConfig, AppConfig,
|
||||
Crypto, ChainPad, CpNetflux, Listmap, Netflux, nThen, Saferphore) {
|
||||
|
||||
|
@ -2471,6 +2472,9 @@ define([
|
|||
try {
|
||||
store.modules['cursor'].leavePad(chanId);
|
||||
} catch (e) { console.error(e); }
|
||||
try {
|
||||
store.modules['integration'].leavePad(chanId);
|
||||
} catch (e) { console.error(e); }
|
||||
try {
|
||||
store.onlyoffice.leavePad(chanId);
|
||||
} catch (e) { console.error(e); }
|
||||
|
@ -2795,6 +2799,7 @@ define([
|
|||
postMessage(clientId, 'LOADING_DRIVE', data);
|
||||
});
|
||||
loadUniversal(Cursor, 'cursor', waitFor);
|
||||
loadUniversal(Integration, 'integration', waitFor);
|
||||
loadOnlyOffice();
|
||||
loadUniversal(Messenger, 'messenger', waitFor);
|
||||
store.messenger = store.modules['messenger'];
|
||||
|
@ -3110,6 +3115,7 @@ define([
|
|||
// To be able to use all the features inside the pad, we need to
|
||||
// initialize the chat (messenger) and the cursor modules.
|
||||
loadUniversal(Cursor, 'cursor', function () {});
|
||||
loadUniversal(Integration, 'integration', function () {});
|
||||
loadUniversal(Messenger, 'messenger', function () {});
|
||||
store.messenger = store.modules['messenger'];
|
||||
|
||||
|
@ -3198,7 +3204,9 @@ define([
|
|||
|
||||
// First tab, no user hash, no anon hash and this app doesn't need a drive
|
||||
// ==> don't create a drive
|
||||
if (data.noDrive && !data.userHash && !data.anonHash) {
|
||||
// Or "neverDrive" (integration into another platform?)
|
||||
// ==> don't create a drive
|
||||
if (data.neverDrive || (data.noDrive && !data.userHash && !data.anonHash)) {
|
||||
return void onNoDrive(clientId, function (obj) {
|
||||
if (obj && obj.error) {
|
||||
// if we can't properly initialize the noDrive mode, use normal mode
|
||||
|
|
|
@ -0,0 +1,205 @@
|
|||
define([
|
||||
'/common/common-util.js',
|
||||
'/common/common-constants.js',
|
||||
'/customize/messages.js',
|
||||
'/customize/application_config.js',
|
||||
'/components/chainpad-crypto/crypto.js',
|
||||
], function (Util, Constants, Messages, AppConfig, Crypto) {
|
||||
var Integration = {};
|
||||
|
||||
var convertToUint8 = function (obj) {
|
||||
var l = Object.keys(obj).length;
|
||||
var u = new Uint8Array(l);
|
||||
for (var i = 0; i<l; i++) {
|
||||
u[i] = obj[i];
|
||||
}
|
||||
return u;
|
||||
};
|
||||
|
||||
var sendMsg = function (ctx, data, client, cb) {
|
||||
var c = ctx.clients[client];
|
||||
if (!c) { return void cb({error: 'NO_CLIENT'}); }
|
||||
var chan = ctx.channels[c.channel];
|
||||
if (!chan) { return void cb({error: 'NO_CHAN'}); }
|
||||
var obj = {
|
||||
id: client,
|
||||
msg: data.msg,
|
||||
uid: data.uid,
|
||||
};
|
||||
chan.sendMsg(JSON.stringify(obj), cb);
|
||||
ctx.emit('MESSAGE', obj, chan.clients.filter(function (cl) {
|
||||
return cl !== client;
|
||||
}));
|
||||
|
||||
};
|
||||
|
||||
var initIntegration = function (ctx, obj, client, cb) {
|
||||
var channel = obj.channel;
|
||||
var secret = obj.secret;
|
||||
if (secret.keys.cryptKey) {
|
||||
secret.keys.cryptKey = convertToUint8(secret.keys.cryptKey);
|
||||
}
|
||||
|
||||
var padChan = secret.channel;
|
||||
var network = ctx.store.network;
|
||||
var first = true;
|
||||
|
||||
var c = ctx.clients[client];
|
||||
if (!c) {
|
||||
c = ctx.clients[client] = {
|
||||
channel: channel
|
||||
};
|
||||
} else {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
var chan = ctx.channels[channel];
|
||||
if (chan) {
|
||||
// This channel is already open in another tab
|
||||
|
||||
// ==> Set the ID to our client object
|
||||
if (!c.id) { c.id = chan.wc.myID + '-' + client; }
|
||||
|
||||
// ==> And push the new tab to the list
|
||||
chan.clients.push(client);
|
||||
|
||||
return void cb();
|
||||
}
|
||||
|
||||
var onOpen = function (wc) {
|
||||
|
||||
ctx.channels[channel] = ctx.channels[channel] || {};
|
||||
|
||||
var chan = ctx.channels[channel];
|
||||
chan.padChan = padChan;
|
||||
|
||||
if (!c.id) { c.id = wc.myID + '-' + client; }
|
||||
if (chan.clients) {
|
||||
// If 2 tabs from the same worker have been opened at the same time,
|
||||
// we have to fix both of them
|
||||
chan.clients.forEach(function (cl) {
|
||||
if (ctx.clients[cl] && !ctx.clients[cl].id) {
|
||||
ctx.clients[cl].id = wc.myID + '-' + cl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
if (!chan.encryptor) { chan.encryptor = Crypto.createEncryptor(secret.keys); }
|
||||
|
||||
wc.on('message', function (cryptMsg) {
|
||||
var msg = chan.encryptor.decrypt(cryptMsg, secret.keys && secret.keys.validateKey);
|
||||
var parsed;
|
||||
try {
|
||||
parsed = JSON.parse(msg);
|
||||
ctx.emit('MESSAGE', parsed, chan.clients);
|
||||
} catch (e) { console.error(e); }
|
||||
});
|
||||
|
||||
chan.wc = wc;
|
||||
chan.sendMsg = function (msg, cb) {
|
||||
cb = cb || function () {};
|
||||
var cmsg = chan.encryptor.encrypt(msg);
|
||||
wc.bcast(cmsg).then(function () {
|
||||
cb();
|
||||
}, function (err) {
|
||||
cb({error: err});
|
||||
});
|
||||
};
|
||||
|
||||
if (!first) { return; }
|
||||
chan.clients = [client];
|
||||
first = false;
|
||||
cb();
|
||||
};
|
||||
|
||||
network.join(channel).then(onOpen, function (err) {
|
||||
return void cb({error: err});
|
||||
});
|
||||
|
||||
var onReconnect = function () {
|
||||
if (!ctx.channels[channel]) { console.log("cant reconnect", channel); return; }
|
||||
network.join(channel).then(onOpen, function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
};
|
||||
ctx.channels[channel] = ctx.channels[channel] || {};
|
||||
ctx.channels[channel].onReconnect = onReconnect;
|
||||
network.on('reconnect', onReconnect);
|
||||
};
|
||||
|
||||
var leaveChannel = function (ctx, padChan) {
|
||||
// Leave channel and prevent reconnect when we leave a pad
|
||||
Object.keys(ctx.channels).some(function (cursorChan) {
|
||||
var channel = ctx.channels[cursorChan];
|
||||
if (channel.padChan !== padChan) { return; }
|
||||
if (channel.wc) { channel.wc.leave(); }
|
||||
if (channel.onReconnect) {
|
||||
var network = ctx.store.network;
|
||||
network.off('reconnect', channel.onReconnect);
|
||||
}
|
||||
delete ctx.channels[cursorChan];
|
||||
return true;
|
||||
});
|
||||
};
|
||||
// Remove the client from all its channels when a tab is closed
|
||||
var removeClient = function (ctx, clientId) {
|
||||
var filter = function (c) {
|
||||
return c !== clientId;
|
||||
};
|
||||
|
||||
// Remove the client from our channels
|
||||
var chan;
|
||||
for (var k in ctx.channels) {
|
||||
chan = ctx.channels[k];
|
||||
chan.clients = chan.clients.filter(filter);
|
||||
if (chan.clients.length === 0) {
|
||||
if (chan.wc) { chan.wc.leave(); }
|
||||
if (chan.onReconnect) {
|
||||
var network = ctx.store.network;
|
||||
network.off('reconnect', chan.onReconnect);
|
||||
}
|
||||
delete ctx.channels[k];
|
||||
}
|
||||
}
|
||||
|
||||
delete ctx.clients[clientId];
|
||||
};
|
||||
|
||||
Integration.init = function (cfg, waitFor, emit) {
|
||||
var integration = {};
|
||||
|
||||
// Already initialized by a "noDrive" tab?
|
||||
if (cfg.store && cfg.store.modules && cfg.store.modules['integration']) {
|
||||
return cfg.store.modules['integration'];
|
||||
}
|
||||
|
||||
var ctx = {
|
||||
store: cfg.store,
|
||||
emit: emit,
|
||||
channels: {},
|
||||
clients: {}
|
||||
};
|
||||
|
||||
integration.removeClient = function (clientId) {
|
||||
removeClient(ctx, clientId);
|
||||
};
|
||||
integration.leavePad = function (padChan) {
|
||||
leaveChannel(ctx, padChan);
|
||||
};
|
||||
integration.execCommand = function (clientId, obj, cb) {
|
||||
var cmd = obj.cmd;
|
||||
var data = obj.data;
|
||||
if (cmd === 'INIT') {
|
||||
return void initIntegration(ctx, data, clientId, cb);
|
||||
}
|
||||
if (cmd === 'SEND') {
|
||||
return void sendMsg(ctx, data, clientId, cb);
|
||||
}
|
||||
};
|
||||
|
||||
return integration;
|
||||
};
|
||||
|
||||
return Integration;
|
||||
});
|
|
@ -63,6 +63,7 @@ define([
|
|||
|
||||
var create = function (options, cb) {
|
||||
var evContentUpdate = Util.mkEvent();
|
||||
var evIntegrationSave = Util.mkEvent();
|
||||
var evCursorUpdate = Util.mkEvent();
|
||||
var evEditableStateChange = Util.mkEvent();
|
||||
var evOnReady = Util.mkEvent(true);
|
||||
|
@ -71,6 +72,7 @@ define([
|
|||
var evStart = Util.mkEvent(true);
|
||||
|
||||
var mediaTagEmbedder;
|
||||
var fileImporter, fileExporter;
|
||||
var $embedButton;
|
||||
|
||||
var common;
|
||||
|
@ -81,6 +83,7 @@ define([
|
|||
var toolbar;
|
||||
var state = STATE.DISCONNECTED;
|
||||
var firstConnection = true;
|
||||
var integration;
|
||||
|
||||
var toolbarContainer = options.toolbarContainer ||
|
||||
(function () { throw new Error("toolbarContainer must be specified"); }());
|
||||
|
@ -233,8 +236,12 @@ define([
|
|||
|
||||
var oldContent;
|
||||
var contentUpdate = function (newContent, waitFor) {
|
||||
if (JSONSortify(newContent) === JSONSortify(oldContent)) { return; }
|
||||
var sNew = JSONSortify(newContent);
|
||||
if (sNew === JSONSortify(oldContent)) { return; }
|
||||
try {
|
||||
if (sNew !== JSONSortify(normalize(oldContent || {}))) {
|
||||
evIntegrationSave.fire();
|
||||
}
|
||||
evContentUpdate.fire(newContent, waitFor);
|
||||
oldContent = newContent;
|
||||
} catch (e) {
|
||||
|
@ -395,6 +402,10 @@ define([
|
|||
};
|
||||
*/
|
||||
|
||||
var integrationOnPatch = function () {
|
||||
cpNfInner.offPatchSent(integrationOnPatch);
|
||||
evIntegrationSave.fire();
|
||||
};
|
||||
onLocal = function (/*padChange*/) {
|
||||
if (unsyncMode) { return; }
|
||||
if (state !== STATE.READY) { return; }
|
||||
|
@ -413,8 +424,14 @@ define([
|
|||
//cpNfInner.metadataMgr.addAuthor();
|
||||
}
|
||||
*/
|
||||
if (integration && oldContent && JSONSortify(content) !== JSONSortify(normalize(oldContent || {}))) {
|
||||
cpNfInner.offPatchSent(integrationOnPatch);
|
||||
cpNfInner.onPatchSent(integrationOnPatch);
|
||||
}
|
||||
|
||||
oldContent = content;
|
||||
|
||||
|
||||
if (Array.isArray(content)) {
|
||||
// Pad
|
||||
content.push({ metadata: cpNfInner.metadataMgr.getMetadataLazy() });
|
||||
|
@ -458,7 +475,8 @@ define([
|
|||
}
|
||||
}
|
||||
|
||||
common.getSframeChannel().on('EV_VERSION_TIME', function (time) {
|
||||
var sframeChan = common.getSframeChannel();
|
||||
sframeChan.on('EV_VERSION_TIME', function (time) {
|
||||
if (!versionHashEl) { return; }
|
||||
var vTime = time;
|
||||
var vTimeStr = vTime ? new Date(vTime).toLocaleString()
|
||||
|
@ -545,7 +563,8 @@ define([
|
|||
contentUpdate(newContent, waitFor);
|
||||
}
|
||||
} else {
|
||||
if (!cpNfInner.metadataMgr.getPrivateData().isNewFile) {
|
||||
var priv = cpNfInner.metadataMgr.getPrivateData();
|
||||
if (!priv.isNewFile) {
|
||||
// We're getting 'new pad' but there is an existing file
|
||||
// We don't know exactly why this can happen but under no circumstances
|
||||
// should we overwrite the content, so lets just try again.
|
||||
|
@ -558,16 +577,23 @@ define([
|
|||
onCorruptedCache();
|
||||
return;
|
||||
}
|
||||
title.updateTitle(title.defaultTitle);
|
||||
evOnDefaultContentNeeded.fire();
|
||||
if (priv.initialState) {
|
||||
var blob = priv.initialState;
|
||||
var file = new File([blob], 'document.'+priv.integrationConfig.fileType);
|
||||
stateChange(STATE.READY); // Required for fileImporter
|
||||
UIElements.importContent('text/plain', waitFor(fileImporter), {})(file);
|
||||
title.updateTitle(file.name);
|
||||
} else {
|
||||
title.updateTitle(title.defaultTitle);
|
||||
evOnDefaultContentNeeded.fire();
|
||||
}
|
||||
}
|
||||
}).nThen(function () {
|
||||
// We have a valid chainpad, reenable cache fix in case with reconnect with
|
||||
// We have a valid chainpad, reenable cache fix in case we reconnect with
|
||||
// a corrupted cache
|
||||
noCache = false;
|
||||
|
||||
stateChange(STATE.READY);
|
||||
firstConnection = false;
|
||||
|
||||
oldContent = undefined;
|
||||
|
||||
|
@ -589,6 +615,52 @@ define([
|
|||
common.getMetadataMgr().setDegraded(false);
|
||||
}
|
||||
|
||||
if (privateDat.integration) {
|
||||
common.openIntegrationChannel(onLocal);
|
||||
var sframeChan = common.getSframeChannel();
|
||||
var integrationSave = function (cb) {
|
||||
var ext = privateDat.integrationConfig.fileType;
|
||||
|
||||
var upload = Util.once(function (_blob) {
|
||||
sframeChan.query('Q_INTEGRATION_SAVE', {
|
||||
blob: _blob
|
||||
}, cb, {
|
||||
raw: true
|
||||
});
|
||||
});
|
||||
|
||||
// "fe" (fileExpoter) can be sync or async depending on the app
|
||||
// we need to handle both cases
|
||||
var syncBlob = fileExporter(function (asyncBlob) {
|
||||
upload(asyncBlob);
|
||||
}, ext);
|
||||
if (syncBlob) {
|
||||
upload(syncBlob);
|
||||
}
|
||||
};
|
||||
const integrationHasUnsavedChanges = function(unsavedChanges, cb) {
|
||||
sframeChan.query('Q_INTEGRATION_HAS_UNSAVED_CHANGES', unsavedChanges, cb);
|
||||
};
|
||||
var inte = common.createIntegration(onLocal, cpNfInner.chainpad,
|
||||
integrationSave, integrationHasUnsavedChanges);
|
||||
if (inte) {
|
||||
integration = true;
|
||||
evIntegrationSave.reg(function () {
|
||||
inte.changed();
|
||||
});
|
||||
}
|
||||
if (firstConnection) {
|
||||
sframeChan.on('Q_INTEGRATION_NEEDSAVE', function (data, cb) {
|
||||
integrationSave(function (obj) {
|
||||
if (obj && obj.error) { console.error(obj.error); }
|
||||
cb();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
firstConnection = false;
|
||||
|
||||
UI.removeLoadingScreen(emitResize);
|
||||
|
||||
if (AppConfig.textAnalyzer && textContentGetter) {
|
||||
|
@ -630,6 +702,7 @@ define([
|
|||
};
|
||||
|
||||
var setFileExporter = function (extension, fe, async) {
|
||||
fileExporter = fe;
|
||||
var $export = common.createButton('export', true, {}, function () {
|
||||
var ext = (typeof(extension) === 'function') ? extension() : extension;
|
||||
var suggestion = title.suggestTitle('cryptpad-document');
|
||||
|
@ -698,31 +771,32 @@ define([
|
|||
|
||||
var setFileImporter = function (options, fi, async) {
|
||||
if (readOnly) { return; }
|
||||
toolbar.$drawer.append(
|
||||
common.createButton('import', true, options, function (c, f) {
|
||||
if (state !== STATE.READY || unsyncMode) {
|
||||
return void UI.warn(Messages.disconnected);
|
||||
}
|
||||
if (async) {
|
||||
fi(c, f, function (content) {
|
||||
nThen(function (waitFor) {
|
||||
contentUpdate(content, waitFor);
|
||||
}).nThen(function () {
|
||||
onLocal();
|
||||
});
|
||||
fileImporter = function (c, f) {
|
||||
if (state !== STATE.READY || unsyncMode) {
|
||||
return void UI.warn(Messages.disconnected);
|
||||
}
|
||||
if (async) {
|
||||
fi(c, f, function (content) {
|
||||
nThen(function (waitFor) {
|
||||
contentUpdate(normalize(content), waitFor);
|
||||
}).nThen(function () {
|
||||
onLocal();
|
||||
});
|
||||
return;
|
||||
}
|
||||
nThen(function (waitFor) {
|
||||
var content = fi(c, f);
|
||||
if (typeof(content) === "undefined") {
|
||||
return void UI.warn(Messages.importError);
|
||||
}
|
||||
contentUpdate(content, waitFor);
|
||||
}).nThen(function () {
|
||||
onLocal();
|
||||
});
|
||||
})
|
||||
return;
|
||||
}
|
||||
nThen(function (waitFor) {
|
||||
var content = fi(c, f);
|
||||
if (typeof(content) === "undefined") {
|
||||
return void UI.warn(Messages.importError);
|
||||
}
|
||||
contentUpdate(normalize(content), waitFor);
|
||||
}).nThen(function () {
|
||||
onLocal();
|
||||
});
|
||||
};
|
||||
toolbar.$drawer.append(
|
||||
common.createButton('import', true, options, fileImporter)
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -6,21 +6,32 @@ define([
|
|||
'/common/sframe-common-outer.js'
|
||||
], function (nThen, ApiConfig, DomReady, SFCommonO) {
|
||||
|
||||
var isIntegration = Boolean(window.CP_integration_outer);
|
||||
var integration = window.CP_integration_outer || {};
|
||||
|
||||
var hash, href;
|
||||
nThen(function (waitFor) {
|
||||
DomReady.onReady(waitFor());
|
||||
}).nThen(function (waitFor) {
|
||||
var obj = SFCommonO.initIframe(waitFor, true);
|
||||
var obj = SFCommonO.initIframe(waitFor, true, integration.pathname);
|
||||
href = obj.href;
|
||||
hash = obj.hash;
|
||||
if (isIntegration) {
|
||||
href = integration.href;
|
||||
hash = integration.hash;
|
||||
}
|
||||
}).nThen(function (/*waitFor*/) {
|
||||
SFCommonO.start({
|
||||
cache: true,
|
||||
cache: !isIntegration,
|
||||
noDrive: true,
|
||||
hash: hash,
|
||||
href: href,
|
||||
useCreationScreen: true,
|
||||
messaging: true
|
||||
useCreationScreen: !isIntegration,
|
||||
messaging: true,
|
||||
integration: isIntegration,
|
||||
integrationUtils: integration.utils,
|
||||
integrationConfig: integration.config || {},
|
||||
initialState: integration.initialState || undefined
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -31,6 +31,8 @@ define([], function () {
|
|||
var versionHash = conf.versionHash;
|
||||
var validateKey = metadata.validateKey;
|
||||
var onConnect = conf.onConnect || function () { };
|
||||
var onError = conf.onError || function () { };
|
||||
var onReady = conf.onReady || function () { };
|
||||
var lastTime; // Time of last patch (if versioned link);
|
||||
conf = undefined;
|
||||
|
||||
|
@ -38,6 +40,7 @@ define([], function () {
|
|||
|
||||
padRpc.onReadyEvent.reg(function () {
|
||||
sframeChan.event('EV_RT_READY', null);
|
||||
onReady();
|
||||
if (lastTime && versionHash) {
|
||||
sframeChan.event('EV_VERSION_TIME', lastTime);
|
||||
}
|
||||
|
@ -143,6 +146,7 @@ define([], function () {
|
|||
});
|
||||
|
||||
padRpc.onErrorEvent.reg(function (err) {
|
||||
onError(err);
|
||||
sframeChan.event('EV_RT_ERROR', err);
|
||||
});
|
||||
|
||||
|
|
|
@ -485,7 +485,7 @@ define([
|
|||
$toolbarContainer.find('#language-mode').val('text');
|
||||
}
|
||||
// return the mode so that the code editor can decide how to display the new content
|
||||
return { content: content, mode: mode };
|
||||
return { content: content, highlightMode: mode, authormarks: {} };
|
||||
};
|
||||
|
||||
exp.setValueAndCursor = function (oldDoc, remoteDoc) {
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
define([
|
||||
'/common/common-util.js',
|
||||
], function (Util) {
|
||||
var module = {};
|
||||
|
||||
module.create = function (
|
||||
Common,
|
||||
onLocal,
|
||||
chainpad,
|
||||
saveHandler,
|
||||
unsavedChangesHandler) {
|
||||
|
||||
var exp = {};
|
||||
var metadataMgr = Common.getMetadataMgr();
|
||||
var privateData = metadataMgr.getPrivateData();
|
||||
|
||||
var config = privateData.integrationConfig;
|
||||
if (!config.autosave) { return; }
|
||||
if (typeof(saveHandler) !== "function") {
|
||||
throw new Error("Incorrect save handler");
|
||||
}
|
||||
if (typeof(unsavedChangesHandler) !== "function") {
|
||||
throw new Error("Incorrect unsaves changes handler");
|
||||
}
|
||||
|
||||
var debug = console.warn;
|
||||
//debug = function () {};
|
||||
var execCommand = function () {}; // placeholder
|
||||
|
||||
var state = {
|
||||
changed: false,
|
||||
last: +new Date(),
|
||||
lastTmp: undefined,
|
||||
other: false, // save lock
|
||||
me: false // save lock
|
||||
};
|
||||
|
||||
var saveTo;
|
||||
var onSettleTo;
|
||||
var alreadySaved = false;
|
||||
|
||||
var BASE_TIMER = config.autosave * 1000;
|
||||
var autosaveTimer = BASE_TIMER;
|
||||
var SETTLE_TO = 3000;
|
||||
var SAVE_TO = Math.min((BASE_TIMER / 2), 10000);
|
||||
|
||||
const setStateChanged = function(newValue) {
|
||||
if (state.changed === newValue) {
|
||||
return;
|
||||
}
|
||||
state.changed = newValue;
|
||||
unsavedChangesHandler(state.changed, function() {});
|
||||
};
|
||||
var requestSave = function () {}; // placeholder
|
||||
var saved = function () {}; // placeholder;
|
||||
var save = function (id) {
|
||||
id = id || Util.uid();
|
||||
requestSave(id, function (allowed) {
|
||||
if (!allowed) { return; }
|
||||
|
||||
var onError = function (err) {
|
||||
state.me = false;
|
||||
// Increase my timer up to 1.5 times the normal timer
|
||||
if (autosaveTimer < (BASE_TIMER*1.5)) {
|
||||
autosaveTimer += 1000;
|
||||
}
|
||||
if (!err) { return; } // "err" is undefined in case of timeout
|
||||
// If err while saving, warn others
|
||||
execCommand('SEND', {
|
||||
msg: 'IFAILED',
|
||||
uid: id
|
||||
}, function (err) {
|
||||
if (err) { console.error(err); }
|
||||
});
|
||||
};
|
||||
var myTo = setTimeout(onError, SAVE_TO);
|
||||
|
||||
saveHandler(function (err) {
|
||||
clearTimeout(myTo);
|
||||
if (err) { return void onError(err); }
|
||||
saved();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
var onMessage = function (data) {
|
||||
if (!data || !data.msg) { return; }
|
||||
debug('Integration onMessage', data);
|
||||
if (data.msg === "ISAVE") {
|
||||
if (state.me) { return; } // I have the lock: abort
|
||||
if (state.other && state.other !== data.uid) { return; } // someone else has the lock
|
||||
// If state.other === data.uid ==> save failed and someone else tries again
|
||||
if (!state.changed) { return; }
|
||||
// If !state.other: nobody has the lock, give them
|
||||
state.other = data.uid;
|
||||
state.lastTmp = +new Date();
|
||||
// Add timeout to make sure the save is done
|
||||
clearTimeout(saveTo);
|
||||
clearTimeout(onSettleTo);
|
||||
|
||||
setStateChanged(false);
|
||||
saveTo = setTimeout(function () {
|
||||
// They weren't able to save in time, try ourselves
|
||||
var id = state.other;
|
||||
state.other = false;
|
||||
save(id);
|
||||
}, SAVE_TO);
|
||||
return;
|
||||
}
|
||||
if (data.msg === "ISAVED") {
|
||||
// Save confirmed: update current state
|
||||
state.last = state.lastTmp || +new Date();
|
||||
state.other = false;
|
||||
state.me = false;
|
||||
alreadySaved = true;
|
||||
// And clear timeout
|
||||
clearTimeout(saveTo);
|
||||
clearTimeout(onSettleTo);
|
||||
// Make sure pending changes will be save at next interval
|
||||
if (state.changed) { exp.changed(); }
|
||||
}
|
||||
if (data.msg === "IFAILED") {
|
||||
if (state.me) { return; } // I already have the lock
|
||||
if (state.other !== data.uid) { return; } // Someone else took the lock
|
||||
state.other = false;
|
||||
if (!state.changed) { return; }
|
||||
clearTimeout(saveTo);
|
||||
clearTimeout(onSettleTo);
|
||||
save();
|
||||
}
|
||||
};
|
||||
|
||||
var onEvent = function (obj) {
|
||||
var cmd = obj.ev;
|
||||
var data = obj.data;
|
||||
if (cmd === 'MESSAGE') {
|
||||
onMessage(data);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
var module = Common.makeUniversal('integration', {
|
||||
onEvent: onEvent
|
||||
});
|
||||
execCommand = module.execCommand;
|
||||
|
||||
// Request a save lock.
|
||||
// Callback with "true" if allowed to save or "false" if someone else
|
||||
// is already saving
|
||||
requestSave = function (id, cb) {
|
||||
if (state.other || state.me) { return void cb(false); } // save in progress
|
||||
debug('Integration send ISAVE');
|
||||
alreadySaved = false; // someone may have saved while we were waiting for our callback
|
||||
execCommand('SEND', {
|
||||
msg: 'ISAVE',
|
||||
uid: id
|
||||
}, function (err) {
|
||||
if (err) { console.error(err); return void cb(false); }
|
||||
debug('Integration cb ISAVE', !(state.other || state.me || alreadySaved));
|
||||
if (state.other || state.me || alreadySaved) {
|
||||
// someone else requested before me
|
||||
return void cb(false);
|
||||
}
|
||||
state.me = true;
|
||||
state.lastTmp = +new Date();
|
||||
setStateChanged(false);
|
||||
cb(true);
|
||||
});
|
||||
};
|
||||
saved = function () {
|
||||
state.last = state.lastTmp || +new Date();
|
||||
state.other = false;
|
||||
state.me = false;
|
||||
debug('Integration send ISAVED');
|
||||
execCommand('SEND', {
|
||||
msg: 'ISAVED'
|
||||
}, function (err) {
|
||||
debug('Integration cb ISAVED');
|
||||
if (err) { console.error(err); }
|
||||
// Make sure pending changes will be save at next interval
|
||||
if (state.changed) { exp.changed(); }
|
||||
});
|
||||
};
|
||||
|
||||
var changedTo;
|
||||
|
||||
// Wait for SETTLE_TO ms without changes to start the saving process
|
||||
var addOnSettleTo = function () {
|
||||
clearTimeout(onSettleTo);
|
||||
onSettleTo = setTimeout(save, SETTLE_TO);
|
||||
};
|
||||
|
||||
exp.changed = function () {
|
||||
setStateChanged(true);
|
||||
|
||||
var timeSinceLastSave = +new Date() - state.last; // in ms
|
||||
var to = autosaveTimer - timeSinceLastSave; // negative if we can save
|
||||
if (to > 0) { // try again in "to"
|
||||
if (!changedTo) { changedTo = setTimeout(exp.changed, to+1); }
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear existing timeouts
|
||||
clearTimeout(changedTo);
|
||||
changedTo = undefined;
|
||||
clearTimeout(onSettleTo);
|
||||
|
||||
// If someone is saving, nothing to do
|
||||
if (state.other || state.me) { return; }
|
||||
|
||||
// We need a save: refresh TO
|
||||
addOnSettleTo();
|
||||
};
|
||||
|
||||
return exp;
|
||||
};
|
||||
|
||||
return module;
|
||||
});
|
||||
|
||||
|
|
@ -15,6 +15,7 @@ define([
|
|||
'pad',
|
||||
'slide',
|
||||
'whiteboard',
|
||||
'integration'
|
||||
].map(function (x) {
|
||||
return `/${x}/`;
|
||||
});
|
||||
|
@ -266,9 +267,10 @@ define([
|
|||
// We're only going to check if a hash exists in the URL or not.
|
||||
Cryptpad.ready(waitFor(), {
|
||||
noDrive: cfg.noDrive && AppConfig.allowDrivelessMode && currentPad.hash,
|
||||
neverDrive: cfg.integration,
|
||||
driveEvents: cfg.driveEvents,
|
||||
cache: Boolean(cfg.cache),
|
||||
currentPad: currentPad
|
||||
currentPad: currentPad,
|
||||
});
|
||||
|
||||
// Remove the login hash if needed
|
||||
|
@ -352,6 +354,13 @@ define([
|
|||
delete sessionStorage.CP_formExportSheet;
|
||||
}
|
||||
|
||||
// New integrated pad
|
||||
if (cfg.initialState) {
|
||||
currentPad.href = cfg.href;
|
||||
currentPad.hash = cfg.hash;
|
||||
return void todo();
|
||||
}
|
||||
|
||||
// New pad options
|
||||
var options = parsed.getOptions();
|
||||
if (options.newPadOpts) {
|
||||
|
@ -674,7 +683,7 @@ define([
|
|||
feedbackAllowed: Utils.Feedback.state,
|
||||
prefersDriveRedirect: Utils.LocalStore.getDriveRedirectPreference(),
|
||||
isPresent: parsed.hashData && parsed.hashData.present,
|
||||
isEmbed: parsed.hashData && parsed.hashData.embed,
|
||||
isEmbed: parsed.hashData && parsed.hashData.embed || cfg.integration,
|
||||
isTop: window.top === window,
|
||||
canEdit: Boolean(hashes && hashes.editHash),
|
||||
oldVersionHash: parsed.hashData && parsed.hashData.version < 2, // password
|
||||
|
@ -695,7 +704,7 @@ define([
|
|||
fromContent: Cryptpad.fromContent,
|
||||
burnAfterReading: burnAfterReading,
|
||||
storeInTeam: Cryptpad.initialTeam || (Cryptpad.initialPath ? -1 : undefined),
|
||||
supportsWasm: Utils.Util.supportsWasm()
|
||||
supportsWasm: Utils.Util.supportsWasm(),
|
||||
};
|
||||
if (window.CryptPad_newSharedFolder) {
|
||||
additionalPriv.newSharedFolder = window.CryptPad_newSharedFolder;
|
||||
|
@ -716,6 +725,13 @@ define([
|
|||
additionalPriv.isChannelMuted = true;
|
||||
}
|
||||
|
||||
// Integration
|
||||
additionalPriv.integration = cfg.integration;
|
||||
additionalPriv.integrationConfig = cfg.integrationConfig;
|
||||
additionalPriv.initialState = cfg.initialState instanceof Blob ?
|
||||
cfg.initialState : undefined;
|
||||
|
||||
// Early access
|
||||
var priv = metaObj.priv;
|
||||
var _plan = typeof(priv.plan) === "undefined" ? Utils.LocalStore.getPremium() : priv.plan;
|
||||
var p = Utils.Util.checkRestrictedApp(parsed.type, AppConfig,
|
||||
|
@ -727,6 +743,7 @@ define([
|
|||
additionalPriv.earlyAccessBlocked = true;
|
||||
}
|
||||
|
||||
// Safe apps
|
||||
if (isSafe) {
|
||||
additionalPriv.hashes = hashes;
|
||||
additionalPriv.password = password;
|
||||
|
@ -742,7 +759,7 @@ define([
|
|||
Utils.LocalStore.setPremium(metaObj.priv.plan);
|
||||
}
|
||||
|
||||
sframeChan.event('EV_METADATA_UPDATE', metaObj);
|
||||
sframeChan.event('EV_METADATA_UPDATE', metaObj, {raw: true});
|
||||
});
|
||||
};
|
||||
Cryptpad.onMetadataChanged(updateMeta);
|
||||
|
@ -1801,6 +1818,18 @@ define([
|
|||
cfg.addRpc(sframeChan, Cryptpad, Utils);
|
||||
}
|
||||
|
||||
sframeChan.on('Q_INTEGRATION_OPENCHANNEL', function (data, cb) {
|
||||
Cryptpad.universal.execCommand({
|
||||
type: 'integration',
|
||||
data: {
|
||||
cmd: 'INIT',
|
||||
data: {
|
||||
channel: data,
|
||||
secret: secret
|
||||
}
|
||||
}
|
||||
}, cb);
|
||||
});
|
||||
sframeChan.on('Q_CURSOR_OPENCHANNEL', function (data, cb) {
|
||||
Cryptpad.universal.execCommand({
|
||||
type: 'cursor',
|
||||
|
@ -1910,6 +1939,23 @@ define([
|
|||
}
|
||||
});
|
||||
|
||||
var integrationSave = function () {};
|
||||
if (cfg.integration) {
|
||||
sframeChan.on('Q_INTEGRATION_SAVE', function (obj, cb) {
|
||||
if (cfg.integrationUtils && cfg.integrationUtils.save) {
|
||||
cfg.integrationUtils.save(obj, cb);
|
||||
}
|
||||
});
|
||||
sframeChan.on('Q_INTEGRATION_HAS_UNSAVED_CHANGES', function (obj, cb) {
|
||||
if (cfg.integrationUtils && cfg.integrationUtils.onHasUnsavedChanges) {
|
||||
cfg.integrationUtils.onHasUnsavedChanges(obj, cb);
|
||||
}
|
||||
});
|
||||
integrationSave = function (cb) {
|
||||
sframeChan.query('Q_INTEGRATION_NEEDSAVE', null, cb);
|
||||
};
|
||||
}
|
||||
|
||||
if (cfg.messaging) {
|
||||
sframeChan.on('Q_CHAT_OPENPADCHAT', function (data, cb) {
|
||||
Cryptpad.universal.execCommand({
|
||||
|
@ -1966,6 +2012,8 @@ define([
|
|||
placeholder.remove();
|
||||
}
|
||||
|
||||
|
||||
|
||||
var replaceHash = function (hash) {
|
||||
// The pad has just been created but is not stored yet. We'll switch
|
||||
// to hidden hash once the pad is stored
|
||||
|
@ -1990,6 +2038,21 @@ define([
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Make sure we add the validateKey to channel metadata when we don't use
|
||||
// the pad creation screen
|
||||
if (!rtConfig.metadata && secret.keys.validateKey) {
|
||||
rtConfig.metadata = {
|
||||
validateKey: secret.keys.validateKey
|
||||
};
|
||||
}
|
||||
if (cfg.integration) {
|
||||
rtConfig.metadata = rtConfig.metadata || {};
|
||||
rtConfig.metadata.selfdestruct = true;
|
||||
}
|
||||
|
||||
|
||||
var ready = false;
|
||||
var cpNfCfg = {
|
||||
sframeChan: sframeChan,
|
||||
channel: secret.channel,
|
||||
|
@ -2009,6 +2072,24 @@ define([
|
|||
}
|
||||
if (readOnly || cfg.noHash) { return; }
|
||||
replaceHash(Utils.Hash.getEditHashFromKeys(secret));
|
||||
},
|
||||
onReady: function () {
|
||||
ready = true;
|
||||
},
|
||||
onError: function () {
|
||||
if (!cfg.integration) { return; }
|
||||
|
||||
var reload = function () {
|
||||
if (cfg.integrationUtils && cfg.integrationUtils.reload) {
|
||||
cfg.integrationUtils.reload();
|
||||
}
|
||||
};
|
||||
|
||||
// on server crash, try to save to Nextcloud
|
||||
if (ready) { return integrationSave(reload); }
|
||||
|
||||
// if error during loading, reload without saving
|
||||
reload();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2017,6 +2098,8 @@ define([
|
|||
Cryptpad.getMetadata(waitFor(function (err, m) {
|
||||
cpNfCfg.owners = [m.priv.edPublic];
|
||||
}));
|
||||
} else if (isNewFile && !cfg.useCreationScreen && cfg.initialState) {
|
||||
console.log('new file with initial state provided');
|
||||
} else if (isNewFile && !cfg.useCreationScreen && currentPad.hash) {
|
||||
console.log("new file with hash in the address bar in an app without pcs and which requires owners");
|
||||
sframeChan.onReady(function () {
|
||||
|
@ -2059,6 +2142,9 @@ define([
|
|||
var rtConfig = {
|
||||
metadata: {}
|
||||
};
|
||||
|
||||
if (cfg.integration) { rtConfig.metadata.selfdestruct = true; }
|
||||
|
||||
if (data.team) {
|
||||
Cryptpad.initialTeam = data.team.id;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ define([
|
|||
'/common/sframe-common-file.js',
|
||||
'/common/sframe-common-codemirror.js',
|
||||
'/common/sframe-common-cursor.js',
|
||||
'/common/sframe-common-integration.js',
|
||||
'/common/sframe-common-mailbox.js',
|
||||
'/common/inner/cache.js',
|
||||
'/common/inner/common-mediatag.js',
|
||||
|
@ -41,6 +42,7 @@ define([
|
|||
File,
|
||||
CodeMirror,
|
||||
Cursor,
|
||||
Integration,
|
||||
Mailbox,
|
||||
Cache,
|
||||
MT,
|
||||
|
@ -131,6 +133,9 @@ define([
|
|||
// Cursor
|
||||
funcs.createCursor = callWithCommon(Cursor.create);
|
||||
|
||||
// Integration
|
||||
funcs.createIntegration = callWithCommon(Integration.create);
|
||||
|
||||
// Files
|
||||
funcs.uploadFile = callWithCommon(File.uploadFile);
|
||||
funcs.createFileManager = callWithCommon(File.create);
|
||||
|
@ -391,6 +396,22 @@ define([
|
|||
});
|
||||
};
|
||||
|
||||
funcs.openIntegrationChannel = function (saveChanges) {
|
||||
var md = JSON.parse(JSON.stringify(ctx.metadataMgr.getMetadata()));
|
||||
var channel = md.integration;
|
||||
if (typeof(channel) !== 'string' || channel.length !== Hash.ephemeralChannelLength) {
|
||||
channel = Hash.createChannelId(true); // true indicates that it's an ephemeral channel
|
||||
}
|
||||
if (md.integration !== channel) {
|
||||
md.integration = channel;
|
||||
ctx.metadataMgr.updateMetadata(md);
|
||||
setTimeout(saveChanges);
|
||||
}
|
||||
ctx.sframeChan.query('Q_INTEGRATION_OPENCHANNEL', channel, function (err, obj) {
|
||||
if (err || (obj && obj.error)) { console.error(err || (obj && obj.error)); }
|
||||
});
|
||||
};
|
||||
|
||||
// CodeMirror
|
||||
funcs.initCodeMirrorApp = callWithCommon(CodeMirror.create);
|
||||
|
||||
|
@ -426,6 +447,9 @@ define([
|
|||
funcs.handleNewFile = function (waitFor, config) {
|
||||
if (window.__CRYPTPAD_TEST__) { return; }
|
||||
var priv = ctx.metadataMgr.getPrivateData();
|
||||
if (priv.isNewFile && priv.initialState) {
|
||||
return void setTimeout(waitFor());
|
||||
}
|
||||
if (priv.isNewFile) {
|
||||
var c = (priv.settings.general && priv.settings.general.creation) || {};
|
||||
// If this is a new file but we have a hash in the URL and pad creation screen is
|
||||
|
|
|
@ -0,0 +1,263 @@
|
|||
(function () {
|
||||
'use strict';
|
||||
var factory = function (/*Hash*/) {
|
||||
|
||||
// This API is used to load a CryptPad editor for a provided document in
|
||||
// an external platform.
|
||||
// The external platform needs to store a session key and make it
|
||||
// available to all users who needs to access the realtime editor.
|
||||
|
||||
var getTxid = function () {
|
||||
return Math.random().toString(16).replace('0.', '');
|
||||
};
|
||||
|
||||
var makeChan = function (iframe, iOrigin) {
|
||||
var handlers = {};
|
||||
var commands = {};
|
||||
|
||||
var iWindow = iframe.contentWindow;
|
||||
var _sendCb = function (txid, args) {
|
||||
iWindow.postMessage({ ack: txid, args: args}, iOrigin);
|
||||
};
|
||||
var onMsg = function (ev) {
|
||||
if (ev.source !== iWindow) { return; }
|
||||
var data = ev.data;
|
||||
|
||||
// On ack
|
||||
if (data.ack) {
|
||||
if (handlers[data.ack]) {
|
||||
handlers[data.ack](data.args);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// On new command
|
||||
var msg = data.msg;
|
||||
var txid = data.txid;
|
||||
if (commands[msg.q]) {
|
||||
console.warn('OUTER RECEIVED QUERY', msg.q, msg.data);
|
||||
commands[msg.q](msg.data, function (args) {
|
||||
_sendCb(txid, args);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
|
||||
var send = function (q, data, cb) {
|
||||
var txid = getTxid();
|
||||
if (cb) { handlers[txid] = cb; }
|
||||
|
||||
console.warn('OUTER SENT QUERY', q, data);
|
||||
iWindow.postMessage({ msg: {
|
||||
q: q,
|
||||
data: data,
|
||||
}, txid: txid}, iOrigin);
|
||||
setTimeout(function () {
|
||||
delete handlers[txid];
|
||||
}, 60000);
|
||||
};
|
||||
var on = function (q, handler) {
|
||||
if (typeof(handler) !== "function") { return; }
|
||||
commands[q] = handler;
|
||||
};
|
||||
|
||||
return {
|
||||
send: send,
|
||||
on: on
|
||||
};
|
||||
};
|
||||
|
||||
var makeIframe = function () {}; // placeholder
|
||||
|
||||
var start = function (config, chan) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(function () {
|
||||
var key = config.document.key;
|
||||
var blob;
|
||||
|
||||
var getBlob = function (cb) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', config.document.url, true);
|
||||
xhr.responseType = 'blob';
|
||||
xhr.onload = function () {
|
||||
if (this.status === 200) {
|
||||
var blob = this.response;
|
||||
// myBlob is now the blob that the object URL pointed to.
|
||||
cb(null, blob);
|
||||
} else {
|
||||
cb(this.status);
|
||||
}
|
||||
};
|
||||
xhr.onerror = function (e) {
|
||||
cb(e.message);
|
||||
};
|
||||
xhr.send();
|
||||
};
|
||||
|
||||
var start = function () {
|
||||
config.document.key = key;
|
||||
chan.send('START', {
|
||||
key: key,
|
||||
application: config.documentType,
|
||||
document: blob,
|
||||
ext: config.document.fileType,
|
||||
autosave: config.autosave || 10
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) { reject(obj.error); return console.error(obj.error); }
|
||||
resolve({});
|
||||
resolve = function () {};
|
||||
reject = function () {};
|
||||
});
|
||||
};
|
||||
|
||||
var onKeyValidated = function () {
|
||||
if (config.document.blob) { // This is a reload
|
||||
blob = config.document.blob;
|
||||
return start();
|
||||
}
|
||||
getBlob(function (err, _blob) {
|
||||
if (err) { reject(err); return console.error(err); }
|
||||
_blob.name = `document.${config.document.fileType}`;
|
||||
blob = _blob;
|
||||
start();
|
||||
});
|
||||
};
|
||||
|
||||
var getSession = function (cb) {
|
||||
chan.send('GET_SESSION', {
|
||||
key: key
|
||||
}, function (obj) {
|
||||
if (obj && obj.error) { reject(obj.error); return console.error(obj.error); }
|
||||
if (obj.key !== key) {
|
||||
// The outside app may reject our new key if the "old" one is deprecated.
|
||||
// This will happen if multiple users try to update the key at the same
|
||||
// time and in this case, only the first user will be able to generate a key.
|
||||
return config.events.onNewKey({
|
||||
old: key,
|
||||
new: obj.key
|
||||
}, function (_key) {
|
||||
// Delay reloading tabs with deprecated key
|
||||
var to = _key !== obj.key ? 1000 : 0;
|
||||
key = _key || obj.key;
|
||||
setTimeout(cb, to);
|
||||
});
|
||||
}
|
||||
cb();
|
||||
});
|
||||
};
|
||||
getSession(onKeyValidated);
|
||||
|
||||
chan.on('SAVE', function (data, cb) {
|
||||
blob = data;
|
||||
config.events.onSave(data, cb);
|
||||
});
|
||||
chan.on('RELOAD', function () {
|
||||
config.document.blob = blob;
|
||||
document.getElementById('cryptpad-editor').remove();
|
||||
makeIframe(config);
|
||||
});
|
||||
chan.on('HAS_UNSAVED_CHANGES', function(unsavedChanges, cb) {
|
||||
config.events.onHasUnsavedChanges(unsavedChanges);
|
||||
cb();
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a CryptPad collaborative editor for the provided document.
|
||||
*
|
||||
* @param {string} cryptpadURL The URL of the CryptPad server.
|
||||
* @param {string} containerID (optional) The ID of the HTML element containing the iframe.
|
||||
* @param {object} config The object containing configuration parameters.
|
||||
* @param {object} config.document The document to load.
|
||||
* @param {string} document.url The document URL.
|
||||
* @param {string} document.fileType The document extension (md, xml, html, etc.).
|
||||
* @param {string} document.key The collaborative session key.
|
||||
* @param {object} config.events Event handlers.
|
||||
* @param {function} events.onSave (blob, callback) The save function to store the document when edited.
|
||||
* @param {function} events.onNewKey (data, callback) The function called when a new key is used.
|
||||
* @param {string} config.documentType The editor to load in CryptPad.
|
||||
* @return {promise}
|
||||
*/
|
||||
var init = function (cryptpadURL, containerId, config) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
setTimeout(function () {
|
||||
|
||||
if (!cryptpadURL || typeof(cryptpadURL) !== "string") {
|
||||
return reject('Missing arg: cryptpadURL');
|
||||
}
|
||||
var container;
|
||||
if (containerId) {
|
||||
container = document.getElementById(containerId);
|
||||
}
|
||||
if (!container) {
|
||||
console.warn('No container provided, append to body');
|
||||
container = document.body;
|
||||
}
|
||||
|
||||
if (!config) { return reject('Missing args: no data provided'); }
|
||||
if(['document.url', 'document.fileType', 'documentType',
|
||||
'events.onSave', 'events.onHasUnsavedChanges',
|
||||
'events.onNewKey'].some(function (k) {
|
||||
var s = k.split('.');
|
||||
var c = config;
|
||||
return s.some(function (key) {
|
||||
if (!c[key]) {
|
||||
reject(`Missing args: no "config.${k}" provided`);
|
||||
return true;
|
||||
}
|
||||
c = c[key];
|
||||
});
|
||||
})) { return; }
|
||||
|
||||
cryptpadURL = cryptpadURL.replace(/(\/)+$/, '');
|
||||
var url = cryptpadURL + '/integration/';
|
||||
var parsed;
|
||||
try {
|
||||
parsed = new URL(url);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return reject('Invalid arg: cryptpadURL');
|
||||
}
|
||||
|
||||
makeIframe = function (config) {
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.setAttribute('id', 'cryptpad-editor');
|
||||
iframe.setAttribute("src", url);
|
||||
container.appendChild(iframe);
|
||||
|
||||
var onMsg = function (msg) {
|
||||
var data = typeof(msg.data) === "string" ? JSON.parse(msg.data) : msg.data;
|
||||
if (!data || data.q !== 'INTEGRATION_READY') { return; }
|
||||
window.removeEventListener('message', onMsg);
|
||||
var chan = makeChan(iframe, parsed.origin);
|
||||
start(config, chan).then(resolve).catch(reject);
|
||||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
};
|
||||
makeIframe(config);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return init;
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (typeof(module) !== 'undefined' && module.exports) {
|
||||
module.exports = factory();
|
||||
} else if ((typeof(define) !== 'undefined' && define !== null) && (define.amd !== null)) {
|
||||
define([], function () {
|
||||
return factory();
|
||||
});
|
||||
} else {
|
||||
window.CryptPadAPI = factory();
|
||||
}
|
||||
}());
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
body.cp-app-diagram {
|
||||
|
||||
@import (once) "../../customize.dist/src/less2/include/browser.less";
|
||||
@import (once) "../../customize.dist/src/less2/include/framework.less";
|
||||
|
||||
.framework_main(
|
||||
@bg-color: @colortheme_apps[diagram],
|
||||
);
|
||||
|
||||
// body
|
||||
&.cp-app-diagram {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
max-height: 100%;
|
||||
min-height: auto;
|
||||
|
||||
.cp-app-diagram-container {
|
||||
display: inline-flex;
|
||||
flex-flow: column;
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
width: 100%;
|
||||
resize: horizontal;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cp-app-diagram-editor {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media (max-width: @browser_media-medium-screen) {
|
||||
.cp-app-diagram-container {
|
||||
flex: 1;
|
||||
max-width: 100%;
|
||||
resize: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,92 @@
|
|||
/* The diagram editor styles should be loaded after the skin and before our overwrites. */
|
||||
/*
|
||||
@import url("mxgraph-editor/3.7.2/styles/grapheditor.css");
|
||||
*/
|
||||
|
||||
body {
|
||||
background-color: #ebebeb;
|
||||
}
|
||||
|
||||
.diagram-editor {
|
||||
height: 110%;
|
||||
min-height: 20px;
|
||||
background-color: #ebebeb;
|
||||
}
|
||||
|
||||
.diagram-editor .geFooterContainer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.diagram-editor input[type="checkbox"], .diagram-editor input[type="radio"],
|
||||
.mxPopupMenu input[type="checkbox"], .mxPopupMenu input[type="radio"],
|
||||
.mxWindow input[type="checkbox"], .mxWindow input[type="radio"],
|
||||
.geDialog input[type="checkbox"], .geDialog input[type="radio"] {
|
||||
vertical-align: text-bottom;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrite XWiki skin styles
|
||||
*/
|
||||
.diagram-editor *,
|
||||
.mxPopupMenu *,
|
||||
.mxWindow *,
|
||||
.geDialog,
|
||||
.geDialog * {
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
.mxPopupMenu,
|
||||
.mxWindow,
|
||||
.geDialog {
|
||||
/* We need the same font size as on draw.io because the dialog height is hard-coded. */
|
||||
font-size: 10pt;
|
||||
}
|
||||
|
||||
.diagram-editor button, .diagram-editor select,
|
||||
.mxPopupMenu button, .mxPopupMenu select,
|
||||
.mxWindow button, .mxWindow select,
|
||||
.geDialog button, .geDialog select {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.diagram-editor input[type="text"],
|
||||
.mxPopupMenu input[type="text"],
|
||||
.mxWindow input[type="text"],
|
||||
.geDialog input[type="text"] {
|
||||
font-size: inherit;
|
||||
height: auto;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
.diagram-editor img,
|
||||
.mxPopupMenu img,
|
||||
.mxWindow img,
|
||||
.geDialog img {
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
.diagram-editor hr,
|
||||
.mxPopupMenu hr,
|
||||
.mxWindow hr,
|
||||
.geDialog hr {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mxPopupMenu table,
|
||||
.mxWindow table,
|
||||
.geDialog table {
|
||||
margin-bottom: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.diagram-editor table > tbody > tr > td,
|
||||
.mxPopupMenu table > tbody > tr > td,
|
||||
.mxWindow table > tbody > tr > td,
|
||||
.geDialog table > tbody > tr > td {
|
||||
border-top: 0 none;
|
||||
}
|
||||
|
||||
.geDialog table > tbody > tr > td {
|
||||
padding: 0;
|
||||
vertical-align: baseline;
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>CryptPad</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="referrer" content="no-referrer" />
|
||||
<script src="/customize/pre-loading.js?ver=1.1"></script>
|
||||
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">
|
||||
<script async data-bootload="/common/sframe-app-outer.js" data-main="/common/boot.js?
|
||||
ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#sbox-iframe {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
bottom: 0px;
|
||||
right: 0px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<noscript></noscript>
|
||||
<iframe-placeholder />
|
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="cp-app-noscroll">
|
||||
<head>
|
||||
<meta content="text/html; charset=utf-8" http-equiv=
|
||||
"content-type">
|
||||
<script async data-bootload="/diagram/inner.js"
|
||||
data-main="/common/sframe-boot.js?ver=1.6"
|
||||
src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
.loading-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
position: fixed;
|
||||
display: none;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0,0,0,0.1);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
#overlay.show {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
<title></title>
|
||||
</head>
|
||||
<body class="cp-app-diagram">
|
||||
<div id="cme_toolbox" class="cp-toolbar-container"></div>
|
||||
<div id="cp-app-diagram-editor" class="cp-app-diagram-editor">
|
||||
<div id="cp-app-diagram-container" class="cp-app-diagram-container">
|
||||
<div class="diagram-editor loading">
|
||||
<div id="overlay"></div>
|
||||
<iframe id="cp-app-diagram-content"
|
||||
src=""
|
||||
border="0"
|
||||
frameborder="0"
|
||||
width="100%"
|
||||
height="100%"
|
||||
name="cp-app-diagram-content"></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,216 @@
|
|||
// This is the initialization loading the CryptPad libraries
|
||||
define([
|
||||
'/common/sframe-app-framework.js',
|
||||
'/customize/messages.js', // translation keys
|
||||
'/components/pako/dist/pako.min.js',
|
||||
'/components/x2js/x2js.js',
|
||||
'/components/tweetnacl/nacl-fast.min.js',
|
||||
'less!/diagram/app-diagram.less',
|
||||
'css!/diagram/drawio.css',
|
||||
], function (
|
||||
Framework,
|
||||
Messages,
|
||||
pako,
|
||||
X2JS) {
|
||||
const Nacl = window.nacl;
|
||||
|
||||
// As described here: https://drawio-app.com/extracting-the-xml-from-mxfiles/
|
||||
const decompressDrawioXml = function(xmlDocStr) {
|
||||
var TEXT_NODE = 3;
|
||||
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(xmlDocStr, "application/xml");
|
||||
|
||||
var errorNode = doc.querySelector("parsererror");
|
||||
if (errorNode) {
|
||||
console.error("error while parsing", errorNode);
|
||||
return xmlDocStr;
|
||||
}
|
||||
|
||||
doc.firstChild.removeAttribute('modified');
|
||||
doc.firstChild.removeAttribute('agent');
|
||||
doc.firstChild.removeAttribute('etag');
|
||||
|
||||
var diagrams = doc.querySelectorAll('diagram');
|
||||
|
||||
diagrams.forEach(function(diagram) {
|
||||
if (diagram.firstChild && diagram.firstChild.nodeType === TEXT_NODE) {
|
||||
const innerText = diagram.firstChild.nodeValue;
|
||||
const bin = Nacl.util.decodeBase64(innerText);
|
||||
const xmlUrlStr = pako.inflateRaw(bin, {to: 'string'});
|
||||
const xmlStr = decodeURIComponent(xmlUrlStr);
|
||||
const diagramDoc = parser.parseFromString(xmlStr, "application/xml");
|
||||
diagram.replaceChild(diagramDoc.firstChild, diagram.firstChild);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var result = new XMLSerializer().serializeToString(doc);
|
||||
return result;
|
||||
};
|
||||
|
||||
const deepEqual = function(o1, o2) {
|
||||
return JSON.stringify(o1) === JSON.stringify(o2);
|
||||
};
|
||||
|
||||
// This is the main initialization loop
|
||||
var onFrameworkReady = function (framework) {
|
||||
var EMPTY_DRAWIO = "<mxfile type=\"embed\"><diagram id=\"bWoO5ACGZIaXrIiKNTKd\" name=\"Page-1\"><mxGraphModel dx=\"1259\" dy=\"718\" grid=\"1\" gridSize=\"10\" guides=\"1\" tooltips=\"1\" connect=\"1\" arrows=\"1\" fold=\"1\" page=\"1\" pageScale=\"1\" pageWidth=\"827\" pageHeight=\"1169\" math=\"0\" shadow=\"0\"><root><mxCell id=\"0\"/><mxCell id=\"1\" parent=\"0\"/></root></mxGraphModel></diagram></mxfile>";
|
||||
var drawioFrame = document.querySelector('#cp-app-diagram-content');
|
||||
var x2js = new X2JS();
|
||||
var lastContent = x2js.xml2js(EMPTY_DRAWIO);
|
||||
var drawIoInitalized = false;
|
||||
|
||||
var postMessageToDrawio = function(msg) {
|
||||
if (!drawIoInitalized) {
|
||||
console.log('draw.io postMessageToDrawio blocked', msg);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('draw.io postMessageToDrawio', msg);
|
||||
drawioFrame.contentWindow.postMessage(JSON.stringify(msg), '*');
|
||||
};
|
||||
|
||||
const jsonContentAsXML = (content) => x2js.js2xml(content);
|
||||
|
||||
var onDrawioInit = function() {
|
||||
drawIoInitalized = true;
|
||||
var xmlStr = jsonContentAsXML(lastContent);
|
||||
postMessageToDrawio({
|
||||
action: 'load',
|
||||
xml: xmlStr,
|
||||
autosave: 1
|
||||
});
|
||||
};
|
||||
|
||||
const numbersToNumbers = function(o) {
|
||||
const type = typeof o;
|
||||
|
||||
if (type === "object") {
|
||||
for (const key in o) {
|
||||
o[key] = numbersToNumbers(o[key]);
|
||||
}
|
||||
return o;
|
||||
} else if (type === 'string' && o.match(/^[+-]?(0|(([1-9]\d*)(\.\d+)?))$/)) {
|
||||
return parseFloat(o, 10);
|
||||
} else {
|
||||
return o;
|
||||
}
|
||||
};
|
||||
|
||||
const xmlAsJsonContent = (xml) => {
|
||||
var decompressedXml = decompressDrawioXml(xml);
|
||||
return numbersToNumbers(x2js.xml2js(decompressedXml));
|
||||
};
|
||||
|
||||
var onDrawioChange = function(newXml) {
|
||||
var newJson = xmlAsJsonContent(newXml);
|
||||
if (!deepEqual(lastContent, newJson)) {
|
||||
lastContent = newJson;
|
||||
framework.localChange();
|
||||
}
|
||||
};
|
||||
|
||||
var onDrawioAutosave = function(data) {
|
||||
onDrawioChange(data.xml);
|
||||
|
||||
// Tell draw.io to hide "Unsaved changes" message
|
||||
postMessageToDrawio({action: 'status', message: '', modified: false});
|
||||
};
|
||||
|
||||
var drawioHandlers = {
|
||||
init: onDrawioInit,
|
||||
autosave: onDrawioAutosave,
|
||||
};
|
||||
|
||||
// This is the function from which you will receive updates from CryptPad
|
||||
framework.onContentUpdate(function (newContent) {
|
||||
lastContent = newContent;
|
||||
var xmlStr = jsonContentAsXML(lastContent);
|
||||
postMessageToDrawio({
|
||||
action: 'merge',
|
||||
xml: xmlStr,
|
||||
});
|
||||
|
||||
framework.localChange();
|
||||
});
|
||||
|
||||
// This is the function called to get the current state of the data in your app
|
||||
framework.setContentGetter(function () {
|
||||
return lastContent;
|
||||
});
|
||||
|
||||
framework.setFileImporter(
|
||||
{accept: ['.drawio', 'application/x-drawio']},
|
||||
(content) => {
|
||||
return xmlAsJsonContent(content);
|
||||
}
|
||||
);
|
||||
|
||||
framework.setFileExporter(
|
||||
'.drawio',
|
||||
() => {
|
||||
return new Blob([jsonContentAsXML(lastContent)], {type: 'application/x-drawio'});
|
||||
}
|
||||
);
|
||||
|
||||
framework.onEditableChange(function () {
|
||||
const editable = !framework.isLocked() && !framework.isReadOnly();
|
||||
postMessageToDrawio({
|
||||
action: 'spinner',
|
||||
message: Messages.reconnecting,
|
||||
show: !editable
|
||||
});
|
||||
|
||||
document.getElementById('overlay').className = editable
|
||||
? ""
|
||||
: "show";
|
||||
});
|
||||
|
||||
// starting the CryptPad framework
|
||||
framework.start();
|
||||
|
||||
drawioFrame.src = '/components/drawio/src/main/webapp/index.html?'
|
||||
+ new URLSearchParams({
|
||||
// pages: 0,
|
||||
// dev: 1,
|
||||
test: 1,
|
||||
stealth: 1,
|
||||
embed: 1,
|
||||
drafts: 0,
|
||||
plugins: 0,
|
||||
|
||||
chrome: framework.isReadOnly() ? 0 : 1,
|
||||
dark: window.CryptPad_theme === "dark" ? 1 : 0,
|
||||
|
||||
// Hide save and exit buttons
|
||||
noSaveBtn: 1,
|
||||
saveAndExit: 0,
|
||||
noExitBtn: 1,
|
||||
|
||||
modified: 'unsavedChanges',
|
||||
proto: 'json',
|
||||
});
|
||||
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.source === drawioFrame.contentWindow) {
|
||||
var data = JSON.parse(event.data);
|
||||
console.log('draw.io got message', data);
|
||||
var eventType = data.event;
|
||||
var handler = drawioHandlers[eventType];
|
||||
if (handler) {
|
||||
handler(data);
|
||||
}
|
||||
}
|
||||
}, false);
|
||||
};
|
||||
|
||||
// Framework initialization
|
||||
Framework.create({
|
||||
toolbarContainer: '#cme_toolbox',
|
||||
contentContainer: '#cp-app-diagram-editor',
|
||||
// validateContent: validateXml,
|
||||
}, function (framework) {
|
||||
onFrameworkReady(framework);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
|
||||
&.cp-app-openin {
|
||||
.framework_min_main();
|
||||
margin: 0;
|
||||
|
||||
header {
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
border-bottom: 1px solid white;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
& > iframe {
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: calc(100% - 100px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
|
||||
<head>
|
||||
<title data-localization="main_title">CryptPad: Collaboration suite, encrypted and open-source</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
|
||||
<script src="/customize/pre-loading.js?ver=1.1"></script>
|
||||
<link href="/customize/src/pre-loading.css?ver=1.0" rel="stylesheet" type="text/css">
|
||||
<script async data-bootload="/integration/main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<link href="/customize/src/outer.css?ver=1.3.2" rel="stylesheet" type="text/css">
|
||||
</head>
|
||||
<body class="html cp-app-integration">
|
||||
<iframe-placeholder>
|
|
@ -0,0 +1,157 @@
|
|||
define([
|
||||
'/common/sframe-common-outer.js',
|
||||
'/common/common-hash.js',
|
||||
], function (SCO, Hash) {
|
||||
|
||||
var getTxid = function () {
|
||||
return Math.random().toString(16).replace('0.', '');
|
||||
};
|
||||
var init = function () {
|
||||
console.warn('INIT');
|
||||
var p = window.parent;
|
||||
var txid = getTxid();
|
||||
p.postMessage(JSON.stringify({ q: 'INTEGRATION_READY', txid: txid }), '*');
|
||||
|
||||
var makeChan = function () {
|
||||
var handlers = {};
|
||||
var commands = {};
|
||||
|
||||
var _sendCb = function (txid, args) {
|
||||
p.postMessage({ ack: txid, args: args}, '*');
|
||||
};
|
||||
var onMsg = function (ev) {
|
||||
if (ev.source !== p) { return; }
|
||||
var data = ev.data;
|
||||
|
||||
// On ack
|
||||
if (data.ack) {
|
||||
if (handlers[data.ack]) {
|
||||
handlers[data.ack](data.args);
|
||||
delete handlers[data.ack];
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// On new command
|
||||
var msg = data.msg;
|
||||
var txid = data.txid;
|
||||
if (commands[msg.q]) {
|
||||
commands[msg.q](msg.data, function (args) {
|
||||
_sendCb(txid, args);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
};
|
||||
window.addEventListener('message', onMsg);
|
||||
|
||||
var send = function (q, data, cb) {
|
||||
var txid = getTxid();
|
||||
if (cb) { handlers[txid] = cb; }
|
||||
p.postMessage({ msg: {
|
||||
q: q,
|
||||
data: data,
|
||||
}, txid: txid}, '*');
|
||||
setTimeout(function () {
|
||||
delete handlers[txid];
|
||||
}, 60000);
|
||||
};
|
||||
var on = function (q, handler) {
|
||||
if (typeof(handler) !== "function") { return; }
|
||||
commands[q] = handler;
|
||||
};
|
||||
|
||||
return {
|
||||
send: send,
|
||||
on: on
|
||||
};
|
||||
};
|
||||
var chan = makeChan();
|
||||
|
||||
var isNew = false;
|
||||
// Make a HEAD request to the servre to check if a file exists in datastore
|
||||
// XXX update nginx config
|
||||
var checkSession = function (oldKey, cb) {
|
||||
var channel = Hash.hrefToHexChannelId(Hash.hashToHref(oldKey));
|
||||
var prefix = channel.slice(0,2);
|
||||
var url = `/datastore/${prefix}/${channel}.ndjson`;
|
||||
|
||||
var http = new XMLHttpRequest();
|
||||
http.open('HEAD', url);
|
||||
http.onreadystatechange = function() {
|
||||
if (this.readyState === this.DONE) {
|
||||
console.error(this.status);
|
||||
if (this.status === 200) {
|
||||
return cb({state: true});
|
||||
}
|
||||
if (this.status === 404) {
|
||||
return cb({state: false});
|
||||
}
|
||||
cb({error: 'Internal server error'});
|
||||
}
|
||||
};
|
||||
http.send();
|
||||
};
|
||||
chan.on('GET_SESSION', function (data, cb) {
|
||||
var getHash = function () {
|
||||
isNew = true;
|
||||
return Hash.createRandomHash('integration');
|
||||
};
|
||||
var oldKey = data.key;
|
||||
if (!oldKey) { return void cb({ key: getHash() }); }
|
||||
|
||||
checkSession(oldKey, function (obj) {
|
||||
if (!obj || obj.error) { return cb(obj); }
|
||||
cb({
|
||||
key: obj.state ? oldKey : getHash()
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
var save = function (obj, cb) {
|
||||
chan.send('SAVE', obj.blob, function (err) {
|
||||
if (err) { return cb({error: err}); }
|
||||
cb();
|
||||
});
|
||||
};
|
||||
var reload = function (data) {
|
||||
chan.send('RELOAD', data);
|
||||
};
|
||||
var onHasUnsavedChanges = function (unsavedChanges, cb) {
|
||||
chan.send('HAS_UNSAVED_CHANGES', unsavedChanges, cb);
|
||||
};
|
||||
|
||||
chan.on('START', function (data) {
|
||||
console.warn('INNER START', data);
|
||||
var href = Hash.hashToHref(data.key, data.application);
|
||||
console.error(Hash.hrefToHexChannelId(href));
|
||||
window.CP_integration_outer = {
|
||||
pathname: `/${data.application}/`,
|
||||
hash: data.key,
|
||||
href: href,
|
||||
initialState: data.document,
|
||||
config: {
|
||||
fileType: data.ext,
|
||||
autosave: data.autosave
|
||||
},
|
||||
utils: {
|
||||
save: save,
|
||||
reload: reload,
|
||||
onHasUnsavedChanges: onHasUnsavedChanges
|
||||
}
|
||||
};
|
||||
require(['/common/sframe-app-outer.js'], function () {
|
||||
console.warn('SAO REQUIRED');
|
||||
delete window.CP_integration_outer;
|
||||
});
|
||||
});
|
||||
|
||||
};
|
||||
init();
|
||||
/*
|
||||
nThen(function (waitFor) {
|
||||
}).nThen(function () {
|
||||
});
|
||||
*/
|
||||
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
@import (reference) '../../customize/src/less2/include/framework.less';
|
||||
|
||||
&.cp-app-openin {
|
||||
.framework_min_main();
|
||||
margin: 0;
|
||||
|
||||
header {
|
||||
height: 100px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
border-bottom: 1px solid white;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
& > iframe {
|
||||
position: fixed;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: calc(100% - 100px);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<!-- If this file is not called customize.dist/src/template.html, it is generated -->
|
||||
<head>
|
||||
<title data-localization="main_title">CryptPad: Collaboration suite, encrypted and open-source</title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="content-type"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<link rel="icon" type="image/png" href="/customize/favicon/main-favicon.png" id="favicon"/>
|
||||
<script async data-bootload="/nextcloud/main.js" data-main="/common/boot.js?ver=1.0" src="/components/requirejs/require.js?ver=2.3.5"></script>
|
||||
<style>
|
||||
#main {
|
||||
color: white;
|
||||
}
|
||||
#cryptpad-editor {
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: calc(100% - 100px);
|
||||
position: absolute;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="html cp-app-openin">
|
||||
<div id="main">Pew pew pew</div>
|
|
@ -0,0 +1,71 @@
|
|||
var url = 'http://localhost:3000';
|
||||
define([
|
||||
'jquery',
|
||||
url + '/cryptpad-api.js'
|
||||
], function ($, Api) {
|
||||
if (window.top !== window) { return; }
|
||||
$(function () {
|
||||
|
||||
console.log(Api);
|
||||
var permaKey = localStorage.CP_test_API_key || '/2/integration/edit/X3RlrgR2JhA0rI+PJ3rXufsQ/'; // XXX
|
||||
var key = window.location.hash ? window.location.hash.slice(1)
|
||||
: permaKey;
|
||||
window.location.hash = key;
|
||||
|
||||
// Test doc
|
||||
var mystring = "Hello World!";
|
||||
var blob = new Blob([mystring], {
|
||||
type: 'text/markdown'
|
||||
});
|
||||
var docUrl = URL.createObjectURL(blob);
|
||||
console.warn(docUrl);
|
||||
|
||||
localStorage.CP_test_API_key = key;
|
||||
|
||||
|
||||
var onSave = function (_blob, cb) {
|
||||
console.log('APP ONSAVE', _blob);
|
||||
docUrl = URL.createObjectURL(_blob);
|
||||
console.log('New doc URL', docUrl);
|
||||
if (typeof (cb) === "function") { cb(); }
|
||||
};
|
||||
var onNewKey = function (data, cb) {
|
||||
// setTimeout hack because Firefox doesn't have locks for localStorage
|
||||
setTimeout(function () {
|
||||
|
||||
var newKey = data.new;
|
||||
var oldKey = data.old;
|
||||
var stored = localStorage.CP_test_API_key;
|
||||
if (stored !== oldKey) {
|
||||
console.error(`Deprecated old key "${oldKey}", can't store the new one "${newKey}"`);
|
||||
window.location.hash = stored;
|
||||
return void cb(stored);
|
||||
}
|
||||
localStorage.CP_test_API_key = newKey;
|
||||
window.location.hash = newKey;
|
||||
cb(newKey);
|
||||
|
||||
}, Math.floor(Math.random()*300));
|
||||
};
|
||||
|
||||
|
||||
Api(url, null, {
|
||||
document: {
|
||||
url: docUrl,
|
||||
key: key,
|
||||
fileType: 'md'
|
||||
},
|
||||
documentType: 'code', // appname
|
||||
events: {
|
||||
onSave: onSave,
|
||||
onNewKey: onNewKey
|
||||
}
|
||||
}).then(function () {
|
||||
console.log('SUCCESS');
|
||||
}).catch(function (e) {
|
||||
console.error('ERROR', e);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|