Merge branch 'drawio-bower' into 5.4-rc

This commit is contained in:
yflory 2023-06-30 12:45:54 +02:00
commit b2788744de
58 changed files with 1960 additions and 106 deletions

View File

@ -1,3 +0,0 @@
{
"directory" : "www/bower_components"
}

12
.editorconfig Normal file
View File

@ -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

View File

@ -1,5 +1,5 @@
[ignore]
.*/bower_components/.*
.*/components/.*
.*/node_modules/lesshint/*
[include]

View File

@ -1,4 +1,5 @@
node_modules/
www/components/
www/bower_components/
www/common/onlyoffice/sdkjs
www/common/onlyoffice/web-apps

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -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

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -40,4 +40,5 @@
<glyph unicode="&#xe91e;" 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="&#xe91f;" 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="&#xe920;" 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="&#xe921;" 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

View File

@ -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";
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -105,6 +105,8 @@ define(req, function(AppConfig, Default, Language) {
extend(messages, Language);
}
messages.type.diagram = "Diagram"; // XXX
messages._languages = map;
messages._languageUsed = language;

View File

@ -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]);

View File

@ -11,7 +11,8 @@
slide: #e57614;
poll: #2c9e98;
form: #2c9e98;
whiteboard: #a72ba7;
whiteboard: #8f40f5;
diagram: #ce3ad3;
kanban: #8C4;
sheet: #40865c;
doc: #5170B5;

View File

@ -11,7 +11,8 @@
slide: #e57614;
poll: #2c9e98;
form: #2c9e98;
whiteboard: #a72ba7;
whiteboard: #8f40f5;
diagram: #ce3ad3;
kanban: #8C4;
sheet: #40865c;
doc: #5170B5;

View File

@ -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}";

View File

@ -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",

View File

@ -220,6 +220,8 @@ module.exports.create = function (config) {
curvePrivate: curve.secretKey,
curvePublic: Nacl.util.encodeBase64(curve.publicKey),
selfDestructTo: {},
};
(function () {

View File

@ -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.

View File

@ -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?

83
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -136,6 +136,7 @@ var appIndexesToBuild = [
'form',
'poll',
'whiteboard',
'diagram',
'slide',
'file',
'calendar',

View File

@ -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);

View File

@ -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"
}));

View File

@ -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;
});

View File

@ -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',

View File

@ -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':

View File

@ -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);
},

View File

@ -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

View File

@ -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 : '';

View File

@ -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

View File

@ -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;
});

View File

@ -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)
);
};

View File

@ -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
});
});
});

View File

@ -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);
});

View File

@ -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) {

View File

@ -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;
});

View File

@ -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;
}

View File

@ -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

263
www/cryptpad-api.js Normal file
View File

@ -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();
}
}());

View File

@ -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;
}
}
}
}

92
www/diagram/drawio.css Normal file
View File

@ -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;
}

38
www/diagram/index.html Normal file
View File

@ -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 />

50
www/diagram/inner.html Normal file
View File

@ -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>

216
www/diagram/inner.js Normal file
View File

@ -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);
});
});

View File

@ -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;
}
}

View File

@ -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>

157
www/integration/main.js Normal file
View File

@ -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 () {
});
*/
});

View File

@ -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;
}
}

26
www/nextcloud/index.html Normal file
View File

@ -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>

71
www/nextcloud/main.js Normal file
View File

@ -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);
});
});
});