feature (org): cool stuff for org mode users ;)
@ -48,5 +48,5 @@ Note that on the FTP and sFTP, sessions connections aren't destroyed on every re
|
||||
Nuage is an open source software with its source code available under the AGPL license. Commercial license and support is available upon request, contact me for details: mickael@kerjean.me
|
||||
|
||||
# Credits
|
||||
- Icons from www.flaticon.com
|
||||
- Folks developing awesome [libraries](https://github.com/mickael-kerjean/nuage/blob/master/package.json) as Nuage is just butter and cream on top.
|
||||
- Iconography: www.flaticon.com, fontawesome.com and material.io
|
||||
- Folks developing awesome [libraries](https://github.com/mickael-kerjean/nuage/blob/master/package.json)
|
||||
|
||||
90
client/assets/img/arrow-down-double.svg
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
aria-hidden="true"
|
||||
data-prefix="fas"
|
||||
data-icon="arrow-alt-to-bottom"
|
||||
role="img"
|
||||
viewBox="0 0 450 512"
|
||||
class="svg-inline--fa fa-arrow-alt-to-bottom fa-w-12 fa-9x"
|
||||
version="1.1"
|
||||
id="svg7697"
|
||||
sodipodi:docname="arrow-down-double.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
width="450"
|
||||
height="512">
|
||||
<metadata
|
||||
id="metadata7703">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7701" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview7699"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="160.34077"
|
||||
inkscape:cy="298.0013"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer3" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="lines"
|
||||
style="display:inline">
|
||||
<path
|
||||
d="m 102.77376,57.71175 v 259.88716 h -79.7 c -17.7999996,0 -26.9165146,14.53938 -14.0999996,30.87906 L 123.17376,494.07066 c 7.62888,9.726 19.67112,9.726 27.3,0 l 114.2,-145.59269 c 12.81652,-16.33968 3.7,-30.87906 -14.1,-30.87906 h -79.8 V 57.71175 c 0,-18.58333 -13.5475,-27.48437 -24,-33.53383 -6.1592,-3.56467 -20,0 -20,0 -13.3,0 -23.86429,14.95148 -24,33.53383 z"
|
||||
class=""
|
||||
id="path7695"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.18205023"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scsssssscsass" />
|
||||
<path
|
||||
sodipodi:nodetypes="sssssssss"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.13474083"
|
||||
id="path3632"
|
||||
class=""
|
||||
d="M 417.23298,74.09757 H 242.11439 c -17.12557,0 -30.90328,-6.7 -30.90328,-20 v -12 c 0,-13.3 13.77771,-20 30.90328,-20 h 175.11859 c 17.12557,0 30.90327,6.7 30.90327,20 v 12 c 0,13.3 -13.7777,20 -30.90327,20 z" />
|
||||
<path
|
||||
d="M 417.23298,172.09757 H 242.11439 c -17.12557,0 -30.90328,-6.7 -30.90328,-20 v -12 c 0,-13.3 13.77771,-20 30.90328,-20 h 175.11859 c 17.12557,0 30.90327,6.7 30.90327,20 v 12 c 0,13.3 -13.7777,20 -30.90327,20 z"
|
||||
class=""
|
||||
id="path3662"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.13474083"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
<path
|
||||
sodipodi:nodetypes="sssssssss"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.13474083"
|
||||
id="path3664"
|
||||
class=""
|
||||
d="M 417.23298,270.09757 H 242.11439 c -17.12557,0 -30.90328,-6.7 -30.90328,-20 v -12 c 0,-13.3 13.77771,-20 30.90328,-20 h 175.11859 c 17.12557,0 30.90327,6.7 30.90327,20 v 12 c 0,13.3 -13.7777,20 -30.90327,20 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
90
client/assets/img/arrow-down.svg
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
aria-hidden="true"
|
||||
data-prefix="fas"
|
||||
data-icon="arrow-alt-to-bottom"
|
||||
role="img"
|
||||
viewBox="0 0 450 512"
|
||||
class="svg-inline--fa fa-arrow-alt-to-bottom fa-w-12 fa-9x"
|
||||
version="1.1"
|
||||
id="svg7697"
|
||||
sodipodi:docname="arrow-down.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
width="450"
|
||||
height="512">
|
||||
<metadata
|
||||
id="metadata7703">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7701" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview7699"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="160.34077"
|
||||
inkscape:cy="298.0013"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer3" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="lines"
|
||||
style="display:inline">
|
||||
<path
|
||||
d="m 102.77376,57.71175 v 259.88716 h -79.7 c -17.7999996,0 -26.9165146,14.53938 -14.0999996,30.87906 L 123.17376,494.07066 c 7.62888,9.726 19.67112,9.726 27.3,0 l 114.2,-145.59269 c 12.81652,-16.33968 3.7,-30.87906 -14.1,-30.87906 h -79.8 V 57.71175 c 0,-18.58333 -13.5475,-27.48437 -24,-33.53383 -6.1592,-3.56467 -20,0 -20,0 -13.3,0 -23.86429,14.95148 -24,33.53383 z"
|
||||
class=""
|
||||
id="path7695"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.18205023"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scsssssscsass" />
|
||||
<path
|
||||
sodipodi:nodetypes="sssssssss"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.13474083"
|
||||
id="path3632"
|
||||
class=""
|
||||
d="M 417.23298,74.09757 H 242.11439 c -17.12557,0 -30.90328,-6.7 -30.90328,-20 v -12 c 0,-13.3 13.77771,-20 30.90328,-20 h 175.11859 c 17.12557,0 30.90327,6.7 30.90327,20 v 12 c 0,13.3 -13.7777,20 -30.90327,20 z" />
|
||||
<path
|
||||
d="M 367.87619,172.09757 H 234.71087 c -13.02278,0 -23.49976,-6.7 -23.49976,-20 v -12 c 0,-13.3 10.47698,-20 23.49976,-20 h 133.16532 c 13.02279,0 23.49976,6.7 23.49976,20 v 12 c 0,13.3 -10.47697,20 -23.49976,20 z"
|
||||
class=""
|
||||
id="path3662"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:0.98952353"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
<path
|
||||
sodipodi:nodetypes="sssssssss"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:0.79045671"
|
||||
id="path3664"
|
||||
class=""
|
||||
d="m 311.18259,270.09757 h -84.97576 c -8.31013,0 -14.99572,-6.7 -14.99572,-20 v -12 c 0,-13.3 6.68559,-20 14.99572,-20 h 84.97576 c 8.31013,0 14.99571,6.7 14.99571,20 v 12 c 0,13.3 -6.68558,20 -14.99571,20 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
90
client/assets/img/arrow-up-double.svg
Normal file
@ -0,0 +1,90 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
aria-hidden="true"
|
||||
data-prefix="fas"
|
||||
data-icon="arrow-alt-to-bottom"
|
||||
role="img"
|
||||
viewBox="0 0 450 512"
|
||||
class="svg-inline--fa fa-arrow-alt-to-bottom fa-w-12 fa-9x"
|
||||
version="1.1"
|
||||
id="svg7697"
|
||||
sodipodi:docname="arrow-up-double.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||
width="450"
|
||||
height="512">
|
||||
<metadata
|
||||
id="metadata7703">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7701" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview7699"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="160.34077"
|
||||
inkscape:cy="298.0013"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="layer3" />
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer3"
|
||||
inkscape:label="lines"
|
||||
style="display:inline">
|
||||
<path
|
||||
d="M 102.77376,466.24703 V 206.35987 h -79.7 c -17.7999996,0 -26.9165146,-14.53938 -14.0999996,-30.87906 L 123.17376,29.888122 c 7.62888,-9.726 19.67112,-9.726 27.3,0 l 114.2,145.592688 c 12.81652,16.33968 3.7,30.87906 -14.1,30.87906 h -79.8 v 259.88716 c 0,18.58333 -13.5475,27.48437 -24,33.53383 -6.1592,3.56467 -20,0 -20,0 -13.3,0 -23.86429,-14.95148 -24,-33.53383 z"
|
||||
class=""
|
||||
id="path7695"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.18205023"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scsssssscsass" />
|
||||
<path
|
||||
sodipodi:nodetypes="sssssssss"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.13474083"
|
||||
id="path3632"
|
||||
class=""
|
||||
d="M 417.23298,302.09757 H 242.11439 c -17.12557,0 -30.90328,-6.7 -30.90328,-20 v -12 c 0,-13.3 13.77771,-20 30.90328,-20 h 175.11859 c 17.12557,0 30.90327,6.7 30.90327,20 v 12 c 0,13.3 -13.7777,20 -30.90327,20 z" />
|
||||
<path
|
||||
d="M 417.23298,400.09757 H 242.11439 c -17.12557,0 -30.90328,-6.7 -30.90328,-20 v -12 c 0,-13.3 13.77771,-20 30.90328,-20 h 175.11859 c 17.12557,0 30.90327,6.7 30.90327,20 v 12 c 0,13.3 -13.7777,20 -30.90327,20 z"
|
||||
class=""
|
||||
id="path3662"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.13474083"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssss" />
|
||||
<path
|
||||
sodipodi:nodetypes="sssssssss"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f2f2f2;fill-opacity:1;stroke-width:1.13474083"
|
||||
id="path3664"
|
||||
class=""
|
||||
d="M 417.23298,498.09757 H 242.11439 c -17.12557,0 -30.90328,-6.7 -30.90328,-20 v -12 c 0,-13.3 13.77771,-20 30.90328,-20 h 175.11859 c 17.12557,0 30.90327,6.7 30.90327,20 v 12 c 0,13.3 -13.7777,20 -30.90327,20 z" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
59
client/assets/img/arrow_right.svg
Normal file
@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
fill="#000000"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
width="24"
|
||||
version="1.1"
|
||||
id="svg855"
|
||||
sodipodi:docname="arrow_right.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata861">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs859" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview857"
|
||||
showgrid="false"
|
||||
inkscape:zoom="9.8333333"
|
||||
inkscape:cx="8.8474576"
|
||||
inkscape:cy="12"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg855" />
|
||||
<path
|
||||
d="M8.59 16.34l4.58-4.59-4.58-4.59L10 5.75l6 6-6 6z"
|
||||
id="path851"
|
||||
style="fill:#000000;fill-opacity:0.53333336" />
|
||||
<path
|
||||
d="M0-.25h24v24H0z"
|
||||
fill="none"
|
||||
id="path853" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
55
client/assets/img/calendar.svg
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg5212"
|
||||
sodipodi:docname="calendar.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata5218">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs5216" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview5214"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="61.388101"
|
||||
inkscape:cy="240.15328"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg5212" />
|
||||
<path
|
||||
d="M 400,64 H 352 V 12 C 352,5.4 346.6,0 340,0 h -40 c -6.6,0 -12,5.4 -12,12 V 64 H 160 V 12 C 160,5.4 154.6,0 148,0 H 108 C 101.4,0 96,5.4 96,12 V 64 H 48 C 21.5,64 0,85.5 0,112 v 352 c 0,26.5 21.5,48 48,48 h 352 c 26.5,0 48,-21.5 48,-48 V 112 C 448,85.5 426.5,64 400,64 Z m -2,404 H 50 c -3.3,0 -6.022147,-2.70007 -6,-6 V 154 h 360 v 308 c 0,3.3 -2.7,6 -6,6 z"
|
||||
id="path5210"
|
||||
style="fill:#000000;fill-opacity:0.53333336"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scssssccsssscsssssssssssccss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
55
client/assets/img/calendar_white.svg
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 448 512"
|
||||
version="1.1"
|
||||
id="svg5212"
|
||||
sodipodi:docname="calendar_white.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata5218">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs5216" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview5214"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="224.38221"
|
||||
inkscape:cy="240.15328"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg5212" />
|
||||
<path
|
||||
d="M 400,64 H 352 V 12 C 352,5.4 346.6,0 340,0 h -40 c -6.6,0 -12,5.4 -12,12 V 64 H 160 V 12 C 160,5.4 154.6,0 148,0 H 108 C 101.4,0 96,5.4 96,12 V 64 H 48 C 21.5,64 0,85.5 0,112 v 352 c 0,26.5 21.5,48 48,48 h 352 c 26.5,0 48,-21.5 48,-48 V 112 C 448,85.5 426.5,64 400,64 Z m -2,404 H 50 c -3.3,0 -6.022147,-2.70007 -6,-6 V 154 h 360 v 308 c 0,3.3 -2.7,6 -6,6 z"
|
||||
id="path5210"
|
||||
style="fill:#f2f2f2;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="scssssccsssscsssssssssssccss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
100
client/assets/img/close.svg
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 51.976 51.976"
|
||||
style="enable-background:new 0 0 51.976 51.976;"
|
||||
xml:space="preserve"
|
||||
width="512px"
|
||||
height="512px"
|
||||
sodipodi:docname="close.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"><metadata
|
||||
id="metadata1544"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs1542" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview1540"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.84375"
|
||||
inkscape:cx="211.15042"
|
||||
inkscape:cy="276.97144"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g1507" />
|
||||
<g
|
||||
id="g1507">
|
||||
<path
|
||||
d="m 36.241,36.241 c -0.781,0.781 -2.047,0.781 -2.828,0 l -7.425,-7.425 -7.778,7.778 c -0.781,0.781 -2.047,0.781 -2.828,0 -0.781,-0.781 -0.781,-2.047 0,-2.828 l 7.778,-7.778 -7.425,-7.425 c -0.781,-0.781 -0.781,-2.048 0,-2.828 0.781,-0.781 2.047,-0.781 2.828,0 l 7.425,7.425 7.071,-7.071 c 0.781,-0.781 2.047,-0.781 2.828,0 0.781,0.781 0.781,2.047 0,2.828 l -7.071,7.071 7.425,7.425 c 0.781,0.781 0.781,2.047 0,2.828 z"
|
||||
id="path1505"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#000000;fill-opacity:0.53333336"
|
||||
sodipodi:nodetypes="sscssscscscssscss" />
|
||||
</g>
|
||||
<g
|
||||
id="g1509">
|
||||
</g>
|
||||
<g
|
||||
id="g1511">
|
||||
</g>
|
||||
<g
|
||||
id="g1513">
|
||||
</g>
|
||||
<g
|
||||
id="g1515">
|
||||
</g>
|
||||
<g
|
||||
id="g1517">
|
||||
</g>
|
||||
<g
|
||||
id="g1519">
|
||||
</g>
|
||||
<g
|
||||
id="g1521">
|
||||
</g>
|
||||
<g
|
||||
id="g1523">
|
||||
</g>
|
||||
<g
|
||||
id="g1525">
|
||||
</g>
|
||||
<g
|
||||
id="g1527">
|
||||
</g>
|
||||
<g
|
||||
id="g1529">
|
||||
</g>
|
||||
<g
|
||||
id="g1531">
|
||||
</g>
|
||||
<g
|
||||
id="g1533">
|
||||
</g>
|
||||
<g
|
||||
id="g1535">
|
||||
</g>
|
||||
<g
|
||||
id="g1537">
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
52
client/assets/img/deadline.svg
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 384 512"
|
||||
version="1.1"
|
||||
id="svg5210"
|
||||
sodipodi:docname="deadline.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata5216">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs5214" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="940"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview5212"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="-11.932203"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg5210" />
|
||||
<path
|
||||
d="M360 64c13.255 0 24-10.745 24-24V24c0-13.255-10.745-24-24-24H24C10.745 0 0 10.745 0 24v16c0 13.255 10.745 24 24 24 0 90.965 51.016 167.734 120.842 192C75.016 280.266 24 357.035 24 448c-13.255 0-24 10.745-24 24v16c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24v-16c0-13.255-10.745-24-24-24 0-90.965-51.016-167.734-120.842-192C308.984 231.734 360 154.965 360 64zM192 208c-57.787 0-104-66.518-104-144h208c0 77.945-46.51 144-104 144z"
|
||||
id="path5208"
|
||||
style="fill:#000000;fill-opacity:0.2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
60
client/assets/img/download_white.svg
Normal file
@ -0,0 +1,60 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
aria-hidden="true"
|
||||
data-prefix="fas"
|
||||
data-icon="arrow-alt-to-bottom"
|
||||
role="img"
|
||||
viewBox="0 0 384 512"
|
||||
class="svg-inline--fa fa-arrow-alt-to-bottom fa-w-12 fa-9x"
|
||||
version="1.1"
|
||||
id="svg7697"
|
||||
sodipodi:docname="download_white.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata7703">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7701" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview7699"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3037281"
|
||||
inkscape:cx="-4.9544349"
|
||||
inkscape:cy="248.41386"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg7697" />
|
||||
<path
|
||||
d="M 360,460 H 24 C 10.7,460 0,453.3 0,440 v -12 c 0,-13.3 10.7,-20 24,-20 h 336 c 13.3,0 24,6.7 24,20 v 12 c 0,13.3 -10.7,20 -24,20 z M 158,56 V 242 H 58.3 c -17.8,0 -28.174568,11.17176 -14.1,22.1 l 134.2,104.2 c 8.37772,6.50491 18.92228,6.50491 27.3,0 L 339.9,264.1 C 353.97457,253.17176 343.6,242 325.8,242 H 226 V 56 c 0,-13.3 -13.5475,-19.670431 -24,-24 -6.1592,-2.551223 -20,0 -20,0 -13.3,0 -23.86429,10.700692 -24,24 z"
|
||||
class=""
|
||||
id="path7695"
|
||||
style="fill:#f2f2f2;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssssscsssssscsass" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.2 KiB |
@ -1,37 +1,98 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Capa_1" x="0px" y="0px" viewBox="0 0 51.976 51.976" style="enable-background:new 0 0 51.976 51.976;" xml:space="preserve" width="512px" height="512px">
|
||||
<g>
|
||||
<path d="M44.373,7.603c-10.137-10.137-26.632-10.138-36.77,0c-10.138,10.138-10.137,26.632,0,36.77s26.632,10.138,36.77,0 C54.51,34.235,54.51,17.74,44.373,7.603z M36.241,36.241c-0.781,0.781-2.047,0.781-2.828,0l-7.425-7.425l-7.778,7.778 c-0.781,0.781-2.047,0.781-2.828,0c-0.781-0.781-0.781-2.047,0-2.828l7.778-7.778l-7.425-7.425c-0.781-0.781-0.781-2.048,0-2.828 c0.781-0.781,2.047-0.781,2.828,0l7.425,7.425l7.071-7.071c0.781-0.781,2.047-0.781,2.828,0c0.781,0.781,0.781,2.047,0,2.828 l-7.071,7.071l7.425,7.425C37.022,34.194,37.022,35.46,36.241,36.241z" fill="#6f6f6f"/>
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 51.976 51.976"
|
||||
style="enable-background:new 0 0 51.976 51.976;"
|
||||
xml:space="preserve"
|
||||
width="512px"
|
||||
height="512px"
|
||||
sodipodi:docname="error.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11"><metadata
|
||||
id="metadata1544"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs1542" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview1540"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.84375"
|
||||
inkscape:cx="211.15042"
|
||||
inkscape:cy="276.97144"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="g1507" />
|
||||
<g
|
||||
id="g1507">
|
||||
<path
|
||||
d="M44.373,7.603c-10.137-10.137-26.632-10.138-36.77,0c-10.138,10.138-10.137,26.632,0,36.77s26.632,10.138,36.77,0 C54.51,34.235,54.51,17.74,44.373,7.603z M36.241,36.241c-0.781,0.781-2.047,0.781-2.828,0l-7.425-7.425l-7.778,7.778 c-0.781,0.781-2.047,0.781-2.828,0c-0.781-0.781-0.781-2.047,0-2.828l7.778-7.778l-7.425-7.425c-0.781-0.781-0.781-2.048,0-2.828 c0.781-0.781,2.047-0.781,2.828,0l7.425,7.425l7.071-7.071c0.781-0.781,2.047-0.781,2.828,0c0.781,0.781,0.781,2.047,0,2.828 l-7.071,7.071l7.425,7.425C37.022,34.194,37.022,35.46,36.241,36.241z"
|
||||
id="path1505"
|
||||
fill="#6f6f6f" />
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1509">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1511">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1513">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1515">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1517">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1519">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1521">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1523">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1525">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1527">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1529">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1531">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1533">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1535">
|
||||
</g>
|
||||
<g>
|
||||
<g
|
||||
id="g1537">
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 2.5 KiB |
54
client/assets/img/more.svg
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 192 512"
|
||||
version="1.1"
|
||||
id="svg846"
|
||||
sodipodi:docname="more.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata852">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs850" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="940"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview848"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.6035156"
|
||||
inkscape:cx="62.635809"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="964"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg846" />
|
||||
<path
|
||||
d="m 96,209.92 c 25.472,0 46.08,20.608 46.08,46.08 0,25.472 -20.608,46.08 -46.08,46.08 -25.472,0 -46.08,-20.608 -46.08,-46.08 0,-25.472 20.608,-46.08 46.08,-46.08 z M 49.92,91.36 c 0,25.472 20.608,46.08 46.08,46.08 25.472,0 46.08,-20.608 46.08,-46.08 0,-25.472 -20.608,-46.08 -46.08,-46.08 -25.472,0 -46.08,20.608 -46.08,46.08 z m 0,329.28 c 0,25.472 20.608,46.08 46.08,46.08 25.472,0 46.08,-20.608 46.08,-46.08 0,-25.472 -20.608,-46.08 -46.08,-46.08 -25.472,0 -46.08,20.608 -46.08,46.08 z"
|
||||
id="path844"
|
||||
style="fill:#000000;fill-opacity:0.53333336;stroke-width:0.63999999"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="sssssssssssssss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
52
client/assets/img/schedule.svg
Normal file
@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 384 512"
|
||||
version="1.1"
|
||||
id="svg4623"
|
||||
sodipodi:docname="schedule.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata4629">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs4627" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="940"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview4625"
|
||||
showgrid="false"
|
||||
inkscape:zoom="0.4609375"
|
||||
inkscape:cx="-269.01695"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="964"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg4623" />
|
||||
<path
|
||||
d="M360 0H24C10.745 0 0 10.745 0 24v16c0 13.255 10.745 24 24 24 0 90.965 51.016 167.734 120.842 192C75.016 280.266 24 357.035 24 448c-13.255 0-24 10.745-24 24v16c0 13.255 10.745 24 24 24h336c13.255 0 24-10.745 24-24v-16c0-13.255-10.745-24-24-24 0-90.965-51.016-167.734-120.842-192C308.984 231.734 360 154.965 360 64c13.255 0 24-10.745 24-24V24c0-13.255-10.745-24-24-24zm-64 448H88c0-77.458 46.204-144 104-144 57.786 0 104 66.517 104 144z"
|
||||
id="path4621"
|
||||
style="fill:#000000;fill-opacity:0.2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
55
client/assets/img/todo_white.svg
Normal file
@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
viewBox="0 0 512 512"
|
||||
version="1.1"
|
||||
id="svg6029"
|
||||
sodipodi:docname="todo_white.svg"
|
||||
inkscape:version="0.92.2 2405546, 2018-03-11">
|
||||
<metadata
|
||||
id="metadata6035">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs6033" />
|
||||
<sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1894"
|
||||
inkscape:window-height="1027"
|
||||
id="namedview6031"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.84375"
|
||||
inkscape:cx="251.21851"
|
||||
inkscape:cy="298.38464"
|
||||
inkscape:window-x="10"
|
||||
inkscape:window-y="37"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="svg6029" />
|
||||
<path
|
||||
d="m 208,128 h 288 c 8.8,0 16,-7.2 16,-16 V 80 c 0,-8.8 -7.2,-16 -16,-16 H 208 c -8.8,0 -16,7.2 -16,16 v 32 c 0,8.8 7.2,16 16,16 z m 0,160 h 288 c 8.8,0 16,-7.2 16,-16 v -32 c 0,-8.8 -7.2,-16 -16,-16 H 208 c -8.8,0 -16,7.2 -16,16 v 32 c 0,8.8 7.2,16 16,16 z m 0,160 h 288 c 8.8,0 16,-7.2 16,-16 v -32 c 0,-8.8 -7.2,-16 -16,-16 H 208 c -8.8,0 -16,7.2 -16,16 v 32 c 0,8.8 7.2,16 16,16 z M 64,368 c -26.5,0 -48.6,21.5 -48.6,48 0,26.5 22.1,48 48.6,48 26.5,0 48,-21.5 48,-48 0,-26.5 -21.5,-48 -48,-48 z M 156.5,69 84.3,141.2 68.7,156.8 c -4.7,4.7 -12.9,4.7 -17.6,0 L 3.5,109.4 c -4.7,-4.7 -4.7,-12.3 0,-17 L 19.2,76.7 c 4.7,-4.7 12.3,-4.7 17,0 l 22.7,22.1 63.7,-63.3 c 4.7,-4.7 12.3,-4.7 17,0 l 17,16.5 c 4.6,4.7 4.6,12.3 -0.1,17 z m 0,159.6 -72.2,72.2 -15.7,15.7 c -4.7,4.7 -12.9,4.7 -17.6,0 L 3.5,269 c -4.7,-4.7 -4.7,-12.3 0,-17 l 15.7,-15.7 c 4.7,-4.7 12.3,-4.7 17,0 l 22.7,22.1 63.7,-63.7 c 4.7,-4.7 12.3,-4.7 17,0 l 17,17 c 4.6,4.6 4.6,12.2 -0.1,16.9 z"
|
||||
id="path6027"
|
||||
style="fill:#f2f2f2;fill-opacity:1"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ssssssssssssssssssssssssssssssssscsccsscccccsscsssssccssss" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
114
client/components/dropdown.js
Normal file
@ -0,0 +1,114 @@
|
||||
/*
|
||||
* This component was build as an alternative to the select component. The idea is
|
||||
* we replace the dirty select on desktop by something more fancy but not on ios/android
|
||||
* as there's just no reason for doing that.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
|
||||
import { Icon, NgIf } from "./";
|
||||
import { debounce } from "../helpers/";
|
||||
import './dropdown.scss';
|
||||
|
||||
export class Dropdown extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
button: false
|
||||
};
|
||||
this.$dropdown = null;
|
||||
this.closeDropdown = this.closeDropdown.bind(this);
|
||||
this.toggleDropdown = this.toggleDropdown.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this.$dropdown = ReactDOM.findDOMNode(this).querySelector(".dropdown_button");
|
||||
// This is not really the "react" way of doing things but we needed to use both a
|
||||
// click on the button and on the body (to exit the dropdown). we had issues
|
||||
// that were impossible to solve the "react" way such as the dropdown button click
|
||||
// event was triggered after the body click which makes it hard to cancel it ...
|
||||
this.$dropdown.addEventListener("click", this.toggleDropdown);
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
this.$dropdown.removeEventListener("click", this.toggleDropdown);
|
||||
document.body.removeEventListener("click", this.closeDropdown);
|
||||
}
|
||||
|
||||
onSelect(name){
|
||||
this.props.onChange(name);
|
||||
}
|
||||
|
||||
closeDropdown(){
|
||||
document.body.removeEventListener("click", this.closeDropdown);
|
||||
this.setState({button: false});
|
||||
}
|
||||
|
||||
toggleDropdown(e){
|
||||
document.body.removeEventListener("click", this.closeDropdown);
|
||||
this.setState({button: !this.state.button}, () => {
|
||||
if(this.state.button === true){
|
||||
requestAnimationFrame(() => {
|
||||
document.body.addEventListener("click", this.closeDropdown);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
const button = this.props.children[0];
|
||||
if(button.type.name !== 'DropdownButton') throw("First children should be of type DropdownButton");
|
||||
|
||||
const dropdown = React.cloneElement(this.props.children[1], {onSelect: this.onSelect.bind(this)});
|
||||
if(dropdown.type.name !== 'DropdownList') throw("Second children should be of type DropdownList");
|
||||
|
||||
return (
|
||||
<div className={"component_dropdown"+(this.state.button ? " active" : "")}>
|
||||
{ button }
|
||||
{ dropdown }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export const DropdownButton = (props) => {
|
||||
return (
|
||||
<div className="dropdown_button">
|
||||
{ props.children }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export const DropdownList = (props) => {
|
||||
const childrens = Array.isArray(props.children) ? props.children : [props.children];
|
||||
return (
|
||||
<div className="dropdown_container">
|
||||
<ul>
|
||||
{
|
||||
childrens.map((children, index) => {
|
||||
const child = React.cloneElement(children, {onSelect: props.onSelect });
|
||||
return (
|
||||
<li key={index}>{child}</li>
|
||||
);
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const DropdownItem = (props) => {
|
||||
return (
|
||||
<div onClick={props.onSelect.bind(null, props.name)}>
|
||||
{props.children}
|
||||
<NgIf cond={!!props.icon} type="inline">
|
||||
<span style={{float: 'right'}}>
|
||||
<Icon name={props.icon} />
|
||||
</span>
|
||||
</NgIf>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
49
client/components/dropdown.scss
Normal file
@ -0,0 +1,49 @@
|
||||
.component_dropdown{
|
||||
position: relative;
|
||||
.dropdown_container{display: none; position: absolute; right: 0;}
|
||||
|
||||
.dropdown_button{
|
||||
border: 1px solid white;
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
min-width: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
.dropdown_container{
|
||||
padding-top: 3px;
|
||||
z-index: 2;
|
||||
ul{
|
||||
margin: 0;
|
||||
list-style-type: none;
|
||||
background: white;
|
||||
border: 1px solid var(--bg-color);
|
||||
box-shadow: 1px 1px 2px rgba(0,0,0,0.1);
|
||||
color: var(--emphasis-secondary);
|
||||
border-radius: 3px;
|
||||
padding: 3px 0px;
|
||||
font-size: 0.92em;
|
||||
li{
|
||||
display: flex;
|
||||
> div{
|
||||
width: 150px;
|
||||
padding: 8px 5px 8px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.component_dropdown{
|
||||
&.active{
|
||||
.dropdown_container{
|
||||
display: block;
|
||||
li:hover{
|
||||
background: var(--bg-color);
|
||||
}
|
||||
}
|
||||
.dropdown_button{
|
||||
border-color: var(--bg-color);
|
||||
box-shadow: 1px 1px 2px rgba(0,0,0,0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -11,11 +11,21 @@ import img_delete from "../assets/img/delete.svg";
|
||||
import img_bucket from "../assets/img/bucket.svg";
|
||||
import img_link from "../assets/img/link.svg";
|
||||
import img_loading from "../assets/img/loader.svg";
|
||||
import img_download from "../assets/img/download.svg";
|
||||
import img_play from "../assets/img/play.svg";
|
||||
import img_pause from "../assets/img/pause.svg";
|
||||
import img_error from "../assets/img/error.svg";
|
||||
import img_loading_white from "../assets/img/loader_white.svg";
|
||||
import img_download_white from "../assets/img/download_white.svg";
|
||||
import img_todo_white from '../assets/img/todo_white.svg';
|
||||
import img_calendar_white from '../assets/img/calendar_white.svg';
|
||||
import img_arrow_right from '../assets/img/arrow_right.svg';
|
||||
import img_more from '../assets/img/more.svg';
|
||||
import img_close from '../assets/img/close.svg';
|
||||
import img_schedule from '../assets/img/schedule.svg';
|
||||
import img_deadline from '../assets/img/deadline.svg';
|
||||
import img_arrow_down from '../assets/img/arrow-down.svg';
|
||||
import img_arrow_up_double from '../assets/img/arrow-up-double.svg';
|
||||
import img_arrow_down_double from '../assets/img/arrow-down-double.svg';
|
||||
|
||||
export const Icon = (props) => {
|
||||
let img;
|
||||
@ -39,8 +49,8 @@ export const Icon = (props) => {
|
||||
img = img_link;
|
||||
}else if(props.name === 'loading'){
|
||||
img = img_loader;
|
||||
}else if(props.name === 'download'){
|
||||
img = img_download;
|
||||
}else if(props.name === 'download_white'){
|
||||
img = img_download_white;
|
||||
}else if(props.name === 'play'){
|
||||
img = img_play;
|
||||
}else if(props.name === 'pause'){
|
||||
@ -49,6 +59,26 @@ export const Icon = (props) => {
|
||||
img = img_error;
|
||||
}else if(props.name === 'loading_white'){
|
||||
img = img_loading_white;
|
||||
}else if(props.name === 'calendar_white'){
|
||||
img = img_calendar_white;
|
||||
}else if(props.name === 'schedule'){
|
||||
img = img_schedule;
|
||||
}else if(props.name === 'deadline'){
|
||||
img = img_deadline;
|
||||
}else if(props.name === 'todo_white'){
|
||||
img = img_todo_white;
|
||||
}else if(props.name === 'arrow_right'){
|
||||
img = img_arrow_right;
|
||||
}else if(props.name === 'more'){
|
||||
img = img_more;
|
||||
}else if(props.name === 'close'){
|
||||
img = img_close;
|
||||
}else if(props.name === 'arrow_up_double'){
|
||||
img = img_arrow_up_double;
|
||||
}else if(props.name === 'arrow_down_double'){
|
||||
img = img_arrow_down_double;
|
||||
}else if(props.name === 'arrow_down'){
|
||||
img = img_arrow_down;
|
||||
}else{
|
||||
throw('unknown icon');
|
||||
}
|
||||
@ -60,4 +90,4 @@ export const Icon = (props) => {
|
||||
src={img}
|
||||
alt={props.name}/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@ -17,6 +17,7 @@ export { Notification } from './notification';
|
||||
export { Alert } from './alert';
|
||||
export { Audio } from './audio';
|
||||
export { Video } from './video';
|
||||
export { Dropdown, DropdownButton, DropdownList, DropdownItem } from './dropdown';
|
||||
//export { Connect } from './connect';
|
||||
// Those are commented because they will delivered as a separate chunk
|
||||
// export { Editor } from './editor';
|
||||
|
||||
@ -3,14 +3,17 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Input, Button, NgIf } from './';
|
||||
import { debounce } from '../helpers/';
|
||||
import './modal.scss';
|
||||
|
||||
export class Modal extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
marginTop: this._marginTop()
|
||||
marginTop: -1
|
||||
};
|
||||
this._resetMargin = debounce(this._resetMargin.bind(this), 100);
|
||||
this._onEscapeKeyPress = this._onEscapeKeyPress.bind(this);
|
||||
}
|
||||
|
||||
onClick(e){
|
||||
@ -20,12 +23,26 @@ export class Modal extends React.Component {
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
this._resetMargin();
|
||||
window.addEventListener("resize", this._resetMargin);
|
||||
window.addEventListener('keydown', this._onEscapeKeyPress);
|
||||
}
|
||||
componentWillUnmount(){
|
||||
window.removeEventListener("resize", this._resetMargin);
|
||||
window.removeEventListener('keydown', this._onEscapeKeyPress);
|
||||
}
|
||||
|
||||
_resetMargin(){
|
||||
this.setState({marginTop: this._marginTop()});
|
||||
}
|
||||
|
||||
componentWillUnmount(){
|
||||
_onEscapeKeyPress(e){
|
||||
if(e.keyCode === 27){
|
||||
this.props.onQuit && this.props.onQuit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
_marginTop(){
|
||||
let size = 300;
|
||||
const $box = document.querySelector('#modal-box > div');
|
||||
@ -45,8 +62,8 @@ export class Modal extends React.Component {
|
||||
transitionAppear={true} transitionAppearTimeout={300}
|
||||
>
|
||||
<NgIf key={"modal-"+this.props.isActive} cond={this.props.isActive}>
|
||||
<div className="component_modal" onClick={this.onClick.bind(this)} id="modal-box">
|
||||
<div key="random" style={{margin: this.state.marginTop+'px auto 0 auto'}}>
|
||||
<div className={"component_modal"+(this.props.className? " " + this.props.className : "")} onClick={this.onClick.bind(this)} id="modal-box">
|
||||
<div style={{margin: this.state.marginTop+'px auto 0 auto', visibility: this.state.marginTop === -1 ? "hidden" : "visible"}}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -57,24 +57,24 @@
|
||||
.modal-appear > div, .modal-enter > div{ opacity: 0;}
|
||||
.modal-appear.modal-appear-active > div, .modal-enter.modal-enter-active > div{
|
||||
opacity: 1;
|
||||
transition: opacity 0.2s ease;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
// box
|
||||
.modal-appear > div > div, .modal-enter > div > div{
|
||||
opacity: 0;
|
||||
transform-origin: top center;
|
||||
transform: translateY(10px);
|
||||
|
||||
}
|
||||
.modal-appear.modal-appear-active > div > div, .modal-enter.modal-enter-active > div > div{
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
transition: all 0.2s ease-out;
|
||||
transition-duration: 0.2s;
|
||||
transition-timing-function: ease-out;
|
||||
transition-property: opacity, transform;
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/***********************/
|
||||
/* LEAVING TRANSITION */
|
||||
// background
|
||||
@ -85,8 +85,9 @@
|
||||
transition-delay: 0.1s;
|
||||
}
|
||||
// box
|
||||
.modal-leave > div > div { opacity: 1; }
|
||||
.modal-leave > div > div { transform: translateY(0); opacity: 1;}
|
||||
.modal-leave.modal-leave-active > div > div{
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
transition: all 0.2s ease-out;
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@ export class ModalPrompt extends React.Component {
|
||||
};
|
||||
this.onCancel = this.onCancel.bind(this);
|
||||
this.onSubmit = this.onSubmit.bind(this);
|
||||
this.onEscapeKeyPress = this.onEscapeKeyPress.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
@ -27,16 +26,12 @@ export class ModalPrompt extends React.Component {
|
||||
fns: {ok: okCallback, cancel: cancelCallback}
|
||||
});
|
||||
});
|
||||
window.addEventListener('keydown', this.onEscapeKeyPress);
|
||||
}
|
||||
|
||||
componentDidUmount(){
|
||||
window.removeEventListener('keydown', this.onEscapeKeyPress);
|
||||
}
|
||||
|
||||
onCancel(){
|
||||
this.setState({appear: false});
|
||||
this.state.fns.cancel();
|
||||
this.state.fns && this.state.fns.cancel();
|
||||
}
|
||||
|
||||
onSubmit(e){
|
||||
|
||||
@ -11,3 +11,4 @@ export { invalidate, http_get, http_post, http_delete } from './ajax';
|
||||
export { screenHeight, screenHeightWithMenubar } from './dom';
|
||||
export { prompt } from './prompt';
|
||||
export { notify } from './notify';
|
||||
export { guid } from './random';
|
||||
|
||||
220
client/helpers/org.js
Normal file
@ -0,0 +1,220 @@
|
||||
import { guid } from "./";
|
||||
|
||||
export function extractTodos(text){
|
||||
const headlines = parse(text);
|
||||
let todos = [];
|
||||
for(let i=0; i < headlines.length; i++){
|
||||
let todo = formatTodo(headlines[i]);
|
||||
if(todo.status){
|
||||
todos.push(todo);
|
||||
}
|
||||
}
|
||||
return todos
|
||||
.sort((a,b) => {
|
||||
if(a.status === "DONE") return +1;
|
||||
else if(a.todo_status === "todo") return -1;
|
||||
return 0;
|
||||
});
|
||||
|
||||
function formatTodo(thing){
|
||||
return {
|
||||
key: thing.header.todo_keyword,
|
||||
id: thing.id,
|
||||
line: thing.header.line,
|
||||
title: thing.header.title,
|
||||
status: thing.header.todo_keyword,
|
||||
todo_status: ["TODO", "NEXT", "DOING", "WAITING"].indexOf(thing.header.todo_keyword) !== -1 ? 'todo' : 'done',
|
||||
is_overdue: _is_overdue(thing.header.todo_keyword, thing.timestamps),
|
||||
tasks: thing.subtasks,
|
||||
tags: thing.header.tags
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function extractEvents(text){
|
||||
const headlines = parse(text);
|
||||
let events = [];
|
||||
for(let i=0; i < headlines.length; i++){
|
||||
events = events.concat(
|
||||
formatEvents(headlines[i])
|
||||
);
|
||||
}
|
||||
return events.sort((a, b) => a.date - b.date);
|
||||
|
||||
function formatEvents(thing){
|
||||
let events = [];
|
||||
for(let i=0; i < thing.timestamps.length; i++){
|
||||
let timestamp = thing.timestamps[i];
|
||||
if(timestamp.active === false) continue;
|
||||
let event = {
|
||||
id: thing.id,
|
||||
line: thing.header.line,
|
||||
title: thing.header.title,
|
||||
status: thing.header.todo_keyword,
|
||||
todo_status: function(keyword){
|
||||
if(!keyword) return null;
|
||||
return ["TODO", "NEXT", "DOING", "WAITING"].indexOf(keyword) !== -1 ? 'todo' : 'done';
|
||||
}(thing.header.todo_keyword),
|
||||
is_overdue: _is_overdue(thing.header.todo_keyword, thing.timestamps),
|
||||
tasks: [],
|
||||
tags: thing.header.tags
|
||||
};
|
||||
if(event.todo_status === 'done') continue;
|
||||
|
||||
event.date = timestamp.timestamp;
|
||||
const today = new Date();
|
||||
today.setHours(23);
|
||||
today.setMinutes(59);
|
||||
today.setSeconds(59);
|
||||
today.setMilliseconds(999);
|
||||
if(event.date < today){
|
||||
event.date = today;
|
||||
}
|
||||
|
||||
event.key = Intl.DateTimeFormat().format(event.date);
|
||||
if(timestamp.repeat){
|
||||
if(timestamp.repeat.interval === "m"){
|
||||
events.push(event);
|
||||
}else{
|
||||
if(timestamp.repeat.interval === "y"){
|
||||
timestamp.repeat.n *= 365;
|
||||
}else if(timestamp.repeat.interval === "w"){
|
||||
timestamp.repeat.n *= 7;
|
||||
}
|
||||
const n_days = timestamp.repeat.n;
|
||||
let today = normalise(new Date());
|
||||
for(let j=0;j<30;j++){
|
||||
if(((today - normalise(timestamp.timestamp)) / 1000*60*60*24) % n_days === 0){
|
||||
event.date = today.getTime();
|
||||
event.key = Intl.DateTimeFormat().format(today);
|
||||
events.push(JSON.parse(JSON.stringify((event))));
|
||||
}
|
||||
today.setDate(today.getDate() + 1);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
events.push(event);
|
||||
}
|
||||
}
|
||||
return events;
|
||||
|
||||
function normalise(date){
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
date.setMilliseconds(0);
|
||||
return date;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function parse(content){
|
||||
let todos = [], todo = reset(), data, text;
|
||||
|
||||
const lines = content.split("\n");
|
||||
for(let i = 0; i<lines.length; i++){
|
||||
text = lines[i];
|
||||
if(data = parse_header(text, i)){
|
||||
if(todo.header){
|
||||
todos.push(todo);
|
||||
todo = reset();
|
||||
}
|
||||
todo.header = data;
|
||||
}else if(data = parse_timestamp(text, i)){
|
||||
todo.timestamps.push(data);
|
||||
}else if(data = parse_subtask(text, i)){
|
||||
todo.subtasks.push(data);
|
||||
}
|
||||
if(i === lines.length - 1 && todo.header){
|
||||
todos.push(todo);
|
||||
}
|
||||
}
|
||||
return todos;
|
||||
|
||||
function reset(){
|
||||
return {id: guid(), timestamps: [], subtasks: []};
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function parse_header(text, line){
|
||||
const match = text.match(/^(\*+)\s(?:([A-Z]{3,})\s){0,1}(?:\[\#([A-C])\]\s){0,1}(.*?)(?:\s+\:((?:[a-z]+\:){1,})){0,1}$/);
|
||||
if(!match) return null;
|
||||
return {
|
||||
line: line,
|
||||
level: RegExp.$1.length,
|
||||
todo_keyword: RegExp.$2 || null,
|
||||
priority: RegExp.$3 || null,
|
||||
title: RegExp.$4 || "Empty Heading",
|
||||
tags: RegExp.$5
|
||||
.replace(/:/g, " ")
|
||||
.trim()
|
||||
.split(" ")
|
||||
.filter((e) => e)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function parse_subtask(text, line){
|
||||
const match = text.match(/(?:-|\d+[\.\)])\s\[([X\s-])\]\s(.*)/);
|
||||
if(!match) return null;
|
||||
return {
|
||||
line: line,
|
||||
status: function(state){
|
||||
if(state === "X") return "DONE";
|
||||
else if(state === " ") return "TODO";
|
||||
return null;
|
||||
}(match[1]),
|
||||
title: match[2] || "Empty task",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function parse_timestamp(text, line){
|
||||
const match = text.match(/(?:([A-Z]+)\:\s){0,1}([<\[])(\d{4}-\d{2}-\d{2})[^>](?:[A-Z][a-z]{2})(?:\s([0-9]{2}\:[0-9]{2})){0,1}(?:\-([0-9]{2}\:[0-9]{2})){0,1}(?:\s(\+{1,2}[0-9]+[dwmy])){0,1}[\>\]](?:--[<\[](\d{4}-\d{2}-\d{2})\s[A-Z][a-z]{2}\s(\d{2}:\d{2}){0,1}[>\]]){0,1}/);
|
||||
if(!match) return null;
|
||||
|
||||
// https://orgmode.org/manual/Timestamps.html
|
||||
return {
|
||||
line: line,
|
||||
keyword: match[1],
|
||||
active: match[2] === "<" ? true : false,
|
||||
timestamp: new Date(match[3] + (match[4] ? " "+match[4] : "")),
|
||||
range: function(start_date, start_time = "", start_time_end, end_date = "", end_time = ""){
|
||||
if(start_time_end && !end_date){
|
||||
return new Date(start_date+" "+start_time_end) - new Date(start_date+" "+start_time);
|
||||
}
|
||||
if(end_date){
|
||||
return new Date(end_date+" "+end_time) - new Date(start_date+" "+start_time);
|
||||
}
|
||||
return null;
|
||||
}(match[3], match[4], match[5], match[7], match[8]),
|
||||
repeat: function(keyword){
|
||||
if(!keyword) return;
|
||||
return {
|
||||
n: parseInt(keyword.replace(/^.*([0-9]+).*$/, "$1")),
|
||||
interval: keyword.replace(/^.*([dwmy])$/, "$1")
|
||||
};
|
||||
}(match[6])
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
function _is_overdue(status, timestamp){
|
||||
if(status !== "TODO") return false;
|
||||
return timestamp.filter((timeObj) => {
|
||||
if(new Date() < timeObj.date) return false;
|
||||
if(timeObj.keyword === "DEADLINE" || timeObj.keyword === "SCHEDULE") return true;
|
||||
return false;
|
||||
}).length > 0 ? true : false;
|
||||
}
|
||||
|
||||
function _date_label(date){
|
||||
date.setHours(0);
|
||||
date.setMinutes(0);
|
||||
date.setSeconds(0);
|
||||
date.setMilliseconds(0);
|
||||
return window.Intl.DateTimeFormat().format(date);
|
||||
}
|
||||
8
client/helpers/random.js
Normal file
@ -0,0 +1,8 @@
|
||||
export function guid(){
|
||||
function s4() {
|
||||
return Math.floor((1 + Math.random()) * 0x10000)
|
||||
.toString(16)
|
||||
.substring(1);
|
||||
}
|
||||
return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
|
||||
}
|
||||
@ -21,6 +21,7 @@ import 'codemirror/addon/fold/foldgutter.css';
|
||||
|
||||
import './editor.scss';
|
||||
import { debounce, screenHeightWithMenubar } from '../../helpers/';
|
||||
import { org_shifttab } from './editor/emacs-org';
|
||||
import config from '../../../config_client';
|
||||
|
||||
export class Editor extends React.Component {
|
||||
@ -32,7 +33,7 @@ export class Editor extends React.Component {
|
||||
filename: this.props.filename,
|
||||
height: 0
|
||||
};
|
||||
this.resetHeight = debounce(this.resetHeight.bind(this), 100);
|
||||
this.resetHeight = debounce(this.resetHeight.bind(this), 0);
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
@ -41,12 +42,31 @@ export class Editor extends React.Component {
|
||||
if(this.state.loading === null) this.setState({loading: true});
|
||||
}, 200);
|
||||
});
|
||||
|
||||
|
||||
this.loadMode(this.props.filename)
|
||||
.then((res) => new Promise((done) => this.setState({loading: false}, () => done(res))))
|
||||
.then(loadCodeMirror.bind(this));
|
||||
.then(loadCodeMirror.bind(this))
|
||||
.then(() => {
|
||||
this.props.event.subscribe((data) => {
|
||||
const [type, value] = data;
|
||||
if(type === "goTo"){
|
||||
const pY = this.state.editor.charCoords({line: value - 1, ch: 0}, "local").top;
|
||||
this.state.editor.scrollTo(null, pY);
|
||||
//this.state.editor.setCursor({line: new_props.currentLine, ch: 2});
|
||||
}else if(type === "refresh"){
|
||||
this.state.editor.setValue(this.props.content);
|
||||
}else if(type === "fold"){
|
||||
this.props.onFoldChange(
|
||||
org_shifttab(this.state.editor)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function loadCodeMirror(data){
|
||||
const [CodeMirror, mode] = data;
|
||||
|
||||
const size_small = 500;
|
||||
let editor = CodeMirror(document.getElementById('editor'), {
|
||||
value: this.props.content,
|
||||
@ -59,11 +79,19 @@ export class Editor extends React.Component {
|
||||
}
|
||||
});
|
||||
|
||||
//debugger;
|
||||
if(!('ontouchstart' in window)) editor.focus();
|
||||
|
||||
if(CodeMirror.afterInit){
|
||||
CodeMirror.afterInit(editor);
|
||||
CodeMirror.afterInit(editor, (key, value) => {
|
||||
if(key === "shifttab"){
|
||||
this.props.onFoldChange(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.setState({editor: editor});
|
||||
this.props.onModeChange(mode);
|
||||
|
||||
editor.on('change', (edit) => {
|
||||
if(this.props.onChange){
|
||||
@ -71,13 +99,9 @@ export class Editor extends React.Component {
|
||||
}
|
||||
});
|
||||
|
||||
if(config.god_editor_mode === true){
|
||||
editor.addKeyMap({
|
||||
"Ctrl-X Ctrl-C": function(cm){
|
||||
history.back();
|
||||
}
|
||||
});
|
||||
}
|
||||
CodeMirror.commands.quit = () => {
|
||||
window.history.back();
|
||||
};
|
||||
|
||||
CodeMirror.commands.save = () => {
|
||||
this.props.onSave && this.props.onSave();
|
||||
|
||||
@ -7,6 +7,14 @@
|
||||
}
|
||||
.CodeMirror-scroll { -webkit-overflow-scrolling: touch; }
|
||||
|
||||
.CodeMirror-foldmarker{
|
||||
visibility: hidden;
|
||||
&:before{
|
||||
content: "...";
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
|
||||
/* SEARCH */
|
||||
.CodeMirror-dialog {
|
||||
position: fixed;
|
||||
@ -42,7 +50,7 @@
|
||||
}
|
||||
|
||||
/* Highlight Theme */
|
||||
.cm-s-default .cm-header {color: #3E7AA6; font-size: 1.15em;}
|
||||
.cm-s-default .cm-header {color: #3E7AA6; font-size: 1.15em; line-height: 1em;}
|
||||
.cm-s-default .cm-keyword {color: #3E7AA6;}
|
||||
.cm-s-default .cm-header.cm-org-level-star{color: #6f6f6f;}
|
||||
.cm-s-default .cm-header.cm-org-todo{color: #FF8355;}
|
||||
|
||||
594
client/pages/viewerpage/editor/emacs-org.js
Normal file
@ -0,0 +1,594 @@
|
||||
export const org_cycle = (cm) => {
|
||||
let pos = cm.getCursor();
|
||||
isFold(cm, pos) ? unfold(cm, pos) : fold(cm, pos);
|
||||
};
|
||||
|
||||
|
||||
let state = {
|
||||
stab: 'CONTENT'
|
||||
};
|
||||
export const org_set_default_fold = (cm) => {
|
||||
set_folding_mode(cm, state.stab);
|
||||
return state.stab;
|
||||
};
|
||||
/*
|
||||
* DONE: Global visibility cycling
|
||||
* TODO: or move to previous table field.
|
||||
*/
|
||||
export const org_shifttab = (cm) => {
|
||||
if(state.stab === "SHOW_ALL"){
|
||||
state.stab = 'OVERVIEW';
|
||||
}else if(state.stab === "OVERVIEW"){
|
||||
state.stab = 'CONTENT';
|
||||
}else if(state.stab === "CONTENT"){
|
||||
state.stab = 'SHOW_ALL';
|
||||
}
|
||||
set_folding_mode(cm, state.stab);
|
||||
return state.stab;
|
||||
};
|
||||
|
||||
|
||||
function set_folding_mode(cm, mode){
|
||||
if(mode === "OVERVIEW"){
|
||||
folding_mode_overview(cm);
|
||||
}else if(mode === "SHOW_ALL"){
|
||||
folding_mode_all(cm);
|
||||
}else if(mode === "CONTENT"){
|
||||
folding_mode_content(cm);
|
||||
}
|
||||
function folding_mode_overview(cm){
|
||||
cm.operation(function() {
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
|
||||
fold(cm, CodeMirror.Pos(i, 0));
|
||||
}
|
||||
});
|
||||
}
|
||||
function folding_mode_content(cm){
|
||||
cm.operation(function() {
|
||||
let previous_header = null;
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
|
||||
fold(cm, CodeMirror.Pos(i, 0));
|
||||
if(/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true){
|
||||
const level = cm.getLine(i).replace(/^(\*+).*/, "$1").length;
|
||||
if(previous_header && level > previous_header.level){
|
||||
unfold(cm, CodeMirror.Pos(previous_header.line, 0));
|
||||
}
|
||||
previous_header = {
|
||||
line: i,
|
||||
level: level
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
function folding_mode_all(cm){
|
||||
cm.operation(function() {
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
|
||||
if(/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true){
|
||||
unfold(cm, CodeMirror.Pos(i, 0));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Promote heading or move table column to left.
|
||||
*/
|
||||
export const org_metaleft = (cm) => {
|
||||
const line = cm.getCursor().line;
|
||||
|
||||
let p = null;
|
||||
if(p = isTitle(cm, line)){
|
||||
if(p['level'] > 1) cm.replaceRange('', {line: p.start, ch: 0}, {line: p.start, ch: 1});
|
||||
}else if(p = isItemList(cm, line)){
|
||||
for(let i=p.start; i<=p.end; i++){
|
||||
if(p['level'] > 0) cm.replaceRange('', {line: i, ch: 0}, {line: i, ch: 2});
|
||||
}
|
||||
}else if(p = isNumberedList(cm, line)){
|
||||
for(let i=p.start; i<=p.end; i++){
|
||||
if(p['level'] > 0) cm.replaceRange('', {line: i, ch: 0}, {line: i, ch: 3});
|
||||
}
|
||||
rearrange_list(cm, line);
|
||||
}
|
||||
};
|
||||
/*
|
||||
* Demote a subtree, a list item or move table column to right.
|
||||
* In front of a drawer or a block keyword, indent it correctly.
|
||||
*/
|
||||
export const org_metaright = (cm) => {
|
||||
const line = cm.getCursor().line;
|
||||
let p = null, tmp = null;
|
||||
|
||||
|
||||
if(p = isTitle(cm, line)){
|
||||
cm.replaceRange('*', {line: p.start, ch: 0});
|
||||
}else if(p = isItemList(cm, line)){
|
||||
if(tmp = isItemList(cm, p.start - 1)){
|
||||
if(p.level < tmp.level + 1){
|
||||
for(let i=p.start; i<=p.end; i++){
|
||||
cm.replaceRange(' ', {line: i, ch: 0});
|
||||
}
|
||||
}
|
||||
}
|
||||
}else if(p = isNumberedList(cm, line)){
|
||||
if(tmp = isNumberedList(cm, p.start - 1)){
|
||||
if(p.level < tmp.level + 1){
|
||||
for(let i=p.start; i<=p.end; i++){
|
||||
cm.replaceRange(' ', {line: i, ch: 0});
|
||||
}
|
||||
rearrange_list(cm, p.start);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Insert a new heading or wrap a region in a table
|
||||
*/
|
||||
export const org_meta_return = (cm) => {
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(cm, line)){
|
||||
const level = p.level;
|
||||
cm.replaceRange('\n'+" ".repeat(level*2)+'- ', {line: p.end, ch: cm.getLine(p.end).length});
|
||||
cm.setCursor({line: p.end+1, ch: level*2+2});
|
||||
}else if(p = isNumberedList(cm, line)){
|
||||
const level = p.level;
|
||||
cm.replaceRange('\n'+" ".repeat(level*3)+(p.n+1)+'. ', {line: p.end, ch: cm.getLine(p.end).length});
|
||||
cm.setCursor({line: p.end+1, ch: level*3+3});
|
||||
rearrange_list(cm, line);
|
||||
}else if(p = isTitle(cm, line)){
|
||||
const tmp = previousOfType(cm, 'title', line);
|
||||
const level = tmp && tmp.level || 1;
|
||||
cm.replaceRange('\n'+'*'.repeat(level)+' ', {line: line, ch: content.length});
|
||||
cm.setCursor({line: line+1, ch: level+1});
|
||||
}else if(content.trim() === ""){
|
||||
cm.replaceRange('* ', {line: line, ch: 0});
|
||||
cm.setCursor({line: line, ch: 2});
|
||||
}else{
|
||||
cm.replaceRange('\n\n* ', {line: line, ch: content.length});
|
||||
cm.setCursor({line: line + 2, ch: 2});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
const TODO_CYCLES = ["TODO", "DONE", ""];
|
||||
/*
|
||||
* Cycle the thing at point or in the current line, depending on context.
|
||||
* Depending on context, this does one of the following:
|
||||
* - TODO: switch a timestamp at point one day into the past
|
||||
* - DONE: on a headline, switch to the previous TODO keyword.
|
||||
* - TODO: on an item, switch entire list to the previous bullet type
|
||||
* - TODO: on a property line, switch to the previous allowed value
|
||||
* - TODO: on a clocktable definition line, move time block into the past
|
||||
*/
|
||||
export const org_shiftleft = (cm) => {
|
||||
const cycles = [].concat(TODO_CYCLES.slice(0).reverse(), TODO_CYCLES.slice(-1)),
|
||||
line = cm.getCursor().line,
|
||||
content = cm.getLine(line),
|
||||
params = isTitle(cm, line);
|
||||
|
||||
if(params === null) return;
|
||||
params['status'] = cycles[cycles.indexOf(params['status']) + 1];
|
||||
cm.replaceRange(makeTitle(params), {line: line, ch: 0}, {line: line, ch: content.length});
|
||||
};
|
||||
/*
|
||||
* Cycle the thing at point or in the current line, depending on context.
|
||||
* Depending on context, this does one of the following:
|
||||
* - TODO: switch a timestamp at point one day into the future
|
||||
* - DONE: on a headline, switch to the next TODO keyword.
|
||||
* - TODO: on an item, switch entire list to the next bullet type
|
||||
* - TODO: on a property line, switch to the next allowed value
|
||||
* - TODO: on a clocktable definition line, move time block into the future
|
||||
*/
|
||||
export const org_shiftright = (cm) => {
|
||||
cm.operation(() => {
|
||||
const cycles = [].concat(TODO_CYCLES, [TODO_CYCLES[0]]),
|
||||
line = cm.getCursor().line,
|
||||
content = cm.getLine(line),
|
||||
params = isTitle(cm, line);
|
||||
|
||||
if(params === null) return;
|
||||
params['status'] = cycles[cycles.indexOf(params['status']) + 1];
|
||||
cm.replaceRange(makeTitle(params), {line: line, ch: 0}, {line: line, ch: content.length});
|
||||
});
|
||||
};
|
||||
|
||||
export const org_insert_todo_heading = (cm) => {
|
||||
cm.operation(() => {
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
|
||||
let p = null;
|
||||
if(p = isItemList(cm, line)){
|
||||
const level = p.level;
|
||||
cm.replaceRange('\n'+" ".repeat(level*2)+'- [ ] ', {line: p.end, ch: cm.getLine(p.end).length});
|
||||
cm.setCursor({line: line+1, ch: 6+level*2});
|
||||
}else if(p = isNumberedList(cm, line)){
|
||||
const level = p.level;
|
||||
cm.replaceRange('\n'+" ".repeat(level*3)+(p.n+1)+'. [ ] ', {line: p.end, ch: cm.getLine(p.end).length});
|
||||
cm.setCursor({line: p.end+1, ch: level*3+7});
|
||||
rearrange_list(cm, line);
|
||||
}else if(p = isTitle(cm, line)){
|
||||
const level = p && p.level || 1;
|
||||
cm.replaceRange('\n'+"*".repeat(level)+' TODO ', {line: line, ch: content.length});
|
||||
cm.setCursor({line: line+1, ch: level+6});
|
||||
}else if(content.trim() === ""){
|
||||
cm.replaceRange('* TODO ', {line: line, ch: 0});
|
||||
cm.setCursor({line: line, ch: 7});
|
||||
}else{
|
||||
cm.replaceRange('\n\n* TODO ', {line: line, ch: content.length});
|
||||
cm.setCursor({line: line + 2, ch: 7});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Move subtree up or move table row up.
|
||||
* Calls ‘org-move-subtree-up’ or ‘org-table-move-row’ or
|
||||
* ‘org-move-item-up’, depending on context
|
||||
*/
|
||||
export const org_metaup = (cm) => {
|
||||
cm.operation(() => {
|
||||
const line = cm.getCursor().line;
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(cm, line)){
|
||||
let a = isItemList(cm, p.start - 1);
|
||||
if(a){
|
||||
swap(cm, [p.start, p.end], [a.start, a.end]);
|
||||
rearrange_list(cm, line);
|
||||
}
|
||||
}else if(p = isNumberedList(cm, line)){
|
||||
let a = isNumberedList(cm, p.start - 1);
|
||||
if(a){
|
||||
swap(cm, [p.start, p.end], [a.start, a.end]);
|
||||
rearrange_list(cm, line);
|
||||
}
|
||||
}else if(p = isTitle(cm, line)){
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Move subtree down or move table row down.
|
||||
* Calls ‘org-move-subtree-down’ or ‘org-table-move-row’ or
|
||||
* ‘org-move-item-down’, depending on context
|
||||
*/
|
||||
export const org_metadown = (cm) => {
|
||||
cm.operation(() => {
|
||||
const line = cm.getCursor().line;
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(cm, line)){
|
||||
let a = isItemList(cm, p.end + 1);
|
||||
if(a){
|
||||
swap(cm, [p.start, p.end], [a.start, a.end]);
|
||||
}
|
||||
}else if(p = isNumberedList(cm, line)){
|
||||
let a = isNumberedList(cm, p.end + 1);
|
||||
if(a){
|
||||
swap(cm, [p.start, p.end], [a.start, a.end]);
|
||||
}
|
||||
rearrange_list(cm, line);
|
||||
}else if(p = isTitle(cm, line)){
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
function makeTitle(p){
|
||||
let content = "*".repeat(p['level'])+" ";
|
||||
if(p['status']){
|
||||
content += p['status']+" ";
|
||||
}
|
||||
content += p['content'];
|
||||
return content;
|
||||
}
|
||||
|
||||
function previousOfType(cm, type, line){
|
||||
let content, tmp, i;
|
||||
for(i=line - 1; i>0; i--){
|
||||
if(type === 'list' || type === null){
|
||||
tmp = isItemList(cm, line);
|
||||
}else if(type === 'numbered' || type === null){
|
||||
tmp = isNumberedList(cm, line);
|
||||
}else if(type === 'title' || type === null){
|
||||
tmp = isTitle(cm, line);
|
||||
}
|
||||
if(tmp !== null){
|
||||
return tmp;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isItemList(cm, line){
|
||||
const rootLineItem = findRootLine(cm, line);
|
||||
if(rootLineItem === null) return null;
|
||||
line = rootLineItem;
|
||||
const content = cm.getLine(line);
|
||||
|
||||
if(content && (content.trimLeft()[0] !== "-" || content.trimLeft()[1] !== " ")) return null;
|
||||
const padding = content.replace(/^(\s*).*$/, "$1").length;
|
||||
if(padding % 2 !== 0) return null;
|
||||
return {
|
||||
type: 'list',
|
||||
level: padding / 2,
|
||||
content: content.trimLeft().replace(/^\s*\-\s(.*)$/, '$1'),
|
||||
start: line,
|
||||
end: function(_cm, _line){
|
||||
let line_candidate = _line,
|
||||
content = null;
|
||||
do{
|
||||
_line += 1;
|
||||
content = _cm.getLine(_line);
|
||||
if(content === undefined || content.trimLeft()[0] === "-"){
|
||||
break;
|
||||
}else if(/^\s+/.test(content)){
|
||||
line_candidate = _line;
|
||||
continue;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}while(_line <= _cm.lineCount())
|
||||
return line_candidate;
|
||||
}(cm, line)
|
||||
};
|
||||
|
||||
function findRootLine(_cm, _line){
|
||||
let content;
|
||||
do{
|
||||
content = _cm.getLine(_line);
|
||||
if(/^\s*\-/.test(content)) return _line;
|
||||
else if(/^\s+/.test(content) === false){
|
||||
break;
|
||||
}
|
||||
_line -= 1;
|
||||
}while(_line >= 0);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
function isNumberedList(cm, line){
|
||||
const rootLineItem = findRootLine(cm, line);
|
||||
if(rootLineItem === null) return null;
|
||||
line = rootLineItem;
|
||||
const content = cm.getLine(line);
|
||||
|
||||
if(/^[0-9]+[\.\)]\s.*$/.test(content && content.trimLeft()) === false) return null;
|
||||
const padding = content.replace(/^(\s*)[0-9]+.*$/, "$1").length;
|
||||
if(padding % 3 !== 0) return null;
|
||||
return {
|
||||
type: 'numbered',
|
||||
level: padding / 3,
|
||||
content: content.trimLeft().replace(/^[0-9]+[\.\)]\s(.*)$/, '$1'),
|
||||
start: line,
|
||||
end: function(_cm, _line){
|
||||
let line_candidate = _line,
|
||||
content = null;
|
||||
do{
|
||||
_line += 1;
|
||||
content = _cm.getLine(_line);
|
||||
if(content === undefined || /^[0-9]+[\.\)]/.test(content.trimLeft())){
|
||||
break;
|
||||
}else if(/^\s+/.test(content)){
|
||||
line_candidate = _line;
|
||||
continue;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}while(_line <= _cm.lineCount())
|
||||
return line_candidate;
|
||||
}(cm, line),
|
||||
// specific
|
||||
n: parseInt(content.trimLeft().replace(/^([0-9]+).*$/, "$1")),
|
||||
separator: content.trimLeft().replace(/^[0-9]+([\.\)]).*$/, '$1')
|
||||
};
|
||||
|
||||
|
||||
function findRootLine(_cm, _line){
|
||||
let content;
|
||||
do{
|
||||
content = _cm.getLine(_line);
|
||||
if(/^\s*[0-9]+[\.\)]\s/.test(content)) return _line;
|
||||
else if(/^\s+/.test(content) === false){
|
||||
break;
|
||||
}
|
||||
_line -= 1;
|
||||
}while(_line >= 0);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
function isTitle(cm, line){
|
||||
const content = cm.getLine(line);
|
||||
if(/^\*+\s/.test(content) === false) return null;
|
||||
const match = content.match(/^(\*+)([\sA-Z]*)\s(.*)$/);
|
||||
if(match === null) return null;
|
||||
return {
|
||||
type: 'title',
|
||||
level: match[1].length,
|
||||
content: match[3],
|
||||
start: line,
|
||||
end: line,
|
||||
// specific
|
||||
status: match[2].trim(),
|
||||
};
|
||||
}
|
||||
|
||||
function rearrange_list(cm, line){
|
||||
const line_inferior = find_limit_inferior(cm, line);
|
||||
const line_superior = find_limit_superior(cm, line);
|
||||
|
||||
let last_p = null, p;
|
||||
|
||||
for(let i=line_inferior; i<=line_superior; i++){
|
||||
if(p = isNumberedList(cm, i)){
|
||||
// rearrange numbers on the numbered list
|
||||
if(last_p){
|
||||
if(p.level === last_p.level){
|
||||
const tmp = findLastAtLevel(cm, p.start, line_inferior, p.level);
|
||||
if(tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1);
|
||||
}else if(p.level > last_p.level){
|
||||
if(p.n !== 1){
|
||||
setNumber(cm, p.start, 1);
|
||||
}
|
||||
}else if(p.level < last_p.level){
|
||||
const tmp = findLastAtLevel(cm, p.start, line_inferior, p.level);
|
||||
if(tmp && p.n !== tmp.n + 1) setNumber(cm, p.start, tmp.n + 1);
|
||||
}
|
||||
}else{
|
||||
if(p.n !== 1){ setNumber(cm, p.start, 1); }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(p = (isNumberedList(cm, i) || isItemList(cm, i))){
|
||||
// rearrange spacing levels in list
|
||||
if(last_p){
|
||||
if(p.level > last_p.level){
|
||||
if(p.level !== last_p.level + 1){
|
||||
setLevel(cm, [p.start, p.end], last_p.level + 1, p.type);
|
||||
}
|
||||
}
|
||||
}else{
|
||||
if(p.level !== 0){
|
||||
setLevel(cm, [p.start, p.end], 0, p.type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
last_p = p;
|
||||
// we can process content block instead of line
|
||||
if(p){
|
||||
i += (p.end - p.start);
|
||||
}
|
||||
}
|
||||
|
||||
function findLastAtLevel(_cm, line, line_limit_inf, level){
|
||||
let p;
|
||||
do{
|
||||
line -= 1;
|
||||
if((p = isNumberedList(_cm, line)) && p.level === level)
|
||||
return p;
|
||||
}while(line > line_limit_inf);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function setLevel(_cm, range, level, type){
|
||||
let content, i;
|
||||
for(i=range[0]; i<=range[1]; i++){
|
||||
content = cm.getLine(i).trimLeft();
|
||||
const n_spaces = function(_level, _line, _type){
|
||||
let spaces = _level * 3;
|
||||
if(_line > 0){
|
||||
spaces += _type === 'numbered' ? 3 : 2;
|
||||
}
|
||||
return spaces;
|
||||
}(level, i - range[0], type)
|
||||
|
||||
content = " ".repeat(n_spaces) + content;
|
||||
cm.replaceRange(content, {line: i, ch: 0}, {line: i, ch: _cm.getLine(i).length});
|
||||
}
|
||||
}
|
||||
|
||||
function setNumber(_cm, line, level){
|
||||
const content = _cm.getLine(line);
|
||||
const new_content = content.replace(/[0-9]+\./, level+".");
|
||||
cm.replaceRange(new_content, {line: line, ch: 0}, {line: line, ch: content.length});
|
||||
}
|
||||
|
||||
function find_limit_inferior(_cm, _line){
|
||||
let content, p, match, line_candidate = _line;
|
||||
do{
|
||||
content = _cm.getLine(_line);
|
||||
p = isNumberedList(_cm, _line);
|
||||
match = /(\s+).*$/.exec(content);
|
||||
if(p){ line_candidate = _line;}
|
||||
if(!p || !match) break;
|
||||
_line -= 1;
|
||||
}while(_line >= 0);
|
||||
return line_candidate;
|
||||
}
|
||||
function find_limit_superior(_cm, _line){
|
||||
let content, p, match, line_candidate = _line;
|
||||
do{
|
||||
content = _cm.getLine(_line);
|
||||
p = isNumberedList(_cm, _line);
|
||||
match = /(\s+).*$/.exec(content);
|
||||
if(p){ line_candidate = _line;}
|
||||
if(!p || !match) break;
|
||||
_line += 1;
|
||||
}while(_line < _cm.lineCount());
|
||||
return line_candidate;
|
||||
}
|
||||
}
|
||||
|
||||
function swap(cm, from, to){
|
||||
const from_content = cm.getRange({line: from[0], ch: 0}, {line: from[1], ch: cm.getLine(from[1]).length}),
|
||||
to_content = cm.getRange({line: to[0], ch: 0}, {line: to[1], ch: cm.getLine(to[1]).length}),
|
||||
cursor = cm.getCursor();
|
||||
|
||||
if(to[0] > from[0]){
|
||||
// moving down
|
||||
cm.replaceRange(
|
||||
from_content,
|
||||
{line: to[0], ch:0},
|
||||
{line: to[1], ch: cm.getLine(to[1]).length}
|
||||
);
|
||||
cm.replaceRange(
|
||||
to_content,
|
||||
{line: from[0], ch:0},
|
||||
{line: from[1], ch: cm.getLine(from[1]).length}
|
||||
);
|
||||
cm.setCursor({
|
||||
line: cursor.line + (to[1] - to[0] + 1),
|
||||
ch: cursor.ch
|
||||
});
|
||||
}else{
|
||||
// moving up
|
||||
cm.replaceRange(
|
||||
to_content,
|
||||
{line: from[0], ch:0},
|
||||
{line: from[1], ch: cm.getLine(from[1]).length}
|
||||
);
|
||||
cm.replaceRange(
|
||||
from_content,
|
||||
{line: to[0], ch:0},
|
||||
{line: to[1], ch: cm.getLine(to[1]).length}
|
||||
);
|
||||
cm.setCursor({
|
||||
line: cursor.line - (to[1] - to[0] + 1),
|
||||
ch: cursor.ch
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function isEmpty(content){
|
||||
return content.trim() === "";
|
||||
}
|
||||
|
||||
export function fold(cm, start){
|
||||
cm.foldCode(start, null, "fold");
|
||||
}
|
||||
export function unfold(cm, start){
|
||||
cm.foldCode(start, null, "unfold");
|
||||
}
|
||||
export function isFold(cm, start){
|
||||
const line = start.line;
|
||||
const marks = cm.findMarks(CodeMirror.Pos(line, 0), CodeMirror.Pos(line + 1, 0));
|
||||
for (let i = 0; i < marks.length; ++i)
|
||||
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
|
||||
return false;
|
||||
}
|
||||
@ -1,10 +1,15 @@
|
||||
import 'codemirror/addon/mode/simple';
|
||||
import {
|
||||
org_cycle, org_shifttab, org_metaleft, org_metaright, org_meta_return, org_metaup,
|
||||
org_metadown, org_insert_todo_heading, org_shiftleft, org_shiftright, fold, unfold,
|
||||
isFold, org_set_default_fold
|
||||
} from './emacs-org';
|
||||
|
||||
CodeMirror.__mode = 'orgmode';
|
||||
|
||||
CodeMirror.defineSimpleMode("orgmode", {
|
||||
start: [
|
||||
{regex: /^(^\*{1,}\s)(TODO|DOING|WAITING|NEXT){0,1}(CANCELLED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED){0,1}(.*)$/, token: ["header org-level-star", "header org-todo", "header org-done", "header"]},
|
||||
{regex: /^(\*{1,}\s)(TODO|DOING|WAITING|NEXT|)(CANCELLED|CANCEL|DEFERRED|DONE|REJECTED|STOP|STOPPED|)(.*?)(\:[\S]+\:|)$/, token: ["header org-level-star","header org-todo","header org-done","header", "comment"]},
|
||||
{regex: /(\+[^\+]+\+)/, token: ["strikethrough"]},
|
||||
{regex: /(\*[^\*]+\*)/, token: ["strong"]},
|
||||
{regex: /(\/[^\/]+\/)/, token: ["em"]},
|
||||
@ -16,7 +21,7 @@ CodeMirror.defineSimpleMode("orgmode", {
|
||||
{regex: /\#\+BEGIN_[A-Z]*/, token: "comment", next: "env"}, // comments
|
||||
{regex: /:?[A-Z_]+\:.*/, token: "comment"}, // property drawers
|
||||
{regex: /(\#\+[A-Z_]*)(\:.*)/, token: ["keyword", 'qualifier']}, // environments
|
||||
{regex: /(CLOCK\:|SHEDULED\:)(\s.+)/, token: ["comment", "keyword"]}
|
||||
{regex: /(CLOCK\:|SHEDULED\:|DEADLINE\:)(\s.+)/, token: ["comment", "keyword"]}
|
||||
],
|
||||
env: [
|
||||
{regex: /.*?\#\+END_[A-Z]*/, token: "comment", next: "start"},
|
||||
@ -90,216 +95,35 @@ CodeMirror.registerGlobalHelper("fold", "drawer", function(mode) {
|
||||
}
|
||||
});
|
||||
|
||||
CodeMirror.afterInit = function(editor){
|
||||
let state = {
|
||||
stab: 'SHOW_ALL'
|
||||
};
|
||||
|
||||
CodeMirror.afterInit = function(editor, fn){
|
||||
|
||||
editor.setOption("extraKeys", {
|
||||
"Tab": function(cm) {
|
||||
let pos = cm.getCursor();
|
||||
isFold(cm, pos) ? unfold(cm, pos) : fold(cm, pos);
|
||||
},
|
||||
"Shift-Tab": function(cm){
|
||||
if(state.stab === "SHOW_ALL"){
|
||||
state.stab = 'OVERVIEW';
|
||||
cm.operation(function() {
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
|
||||
fold(cm, CodeMirror.Pos(i, 0));
|
||||
}
|
||||
});
|
||||
}else if(state.stab === "OVERVIEW"){
|
||||
state.stab = 'CONTENT';
|
||||
cm.operation(function() {
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
|
||||
if(/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true){
|
||||
if(/^\* /.test(cm.getLine(i))){
|
||||
unfold(cm, CodeMirror.Pos(i, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}else if(state.stab === "CONTENT"){
|
||||
state.stab = 'SHOW_ALL';
|
||||
cm.operation(function() {
|
||||
for (var i = cm.firstLine(), e = cm.lastLine(); i <= e; i++){
|
||||
if(/header/.test(cm.getTokenTypeAt(CodeMirror.Pos(i, 0))) === true){
|
||||
unfold(cm, CodeMirror.Pos(i, 0));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
"Alt-Left": function(cm){
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
let p = null;
|
||||
"Tab": function(cm) { org_cycle(cm); },
|
||||
"Shift-Tab": function(cm){ fn('shifttab', org_shifttab(cm)); },
|
||||
"Alt-Left": function(cm){ org_metaleft(cm); },
|
||||
"Alt-Right": function(cm){ org_metaright(cm); },
|
||||
"Alt-Enter": function(cm){ org_meta_return(cm); },
|
||||
"Alt-Up": function(cm){ org_metaup(cm); },
|
||||
"Alt-Down": function(cm){ org_metadown(cm); },
|
||||
"Shift-Alt-Enter": function(cm){ org_insert_todo_heading(cm); },
|
||||
"Shift-Left": function(cm){ org_shiftleft(cm); },
|
||||
"Shift-Right": function(cm){ org_shiftright(cm); },
|
||||
});
|
||||
fn('shifttab', org_set_default_fold(editor));
|
||||
|
||||
if(p = isTitle(content)){
|
||||
if(p['level'] > 1) cm.replaceRange('', {line: line, ch: 0}, {line: line, ch: 1});
|
||||
}else if(p = isItemList(content)){
|
||||
if(p['level'] > 0) cm.replaceRange('', {line: line, ch: 0}, {line: line, ch: 2});
|
||||
}else if(isNumberedList(content)){
|
||||
}
|
||||
},
|
||||
"Alt-Right": function(cm){
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(content)){
|
||||
cm.replaceRange(' ', {line: line, ch: 0});
|
||||
}else if(p = isNumberedList(content)){
|
||||
}else if(p = isTitle(content)){
|
||||
cm.replaceRange('*', {line: line, ch: 0});
|
||||
}
|
||||
},
|
||||
"Alt-Enter": function(cm){
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(content)){
|
||||
const level = p.level;
|
||||
cm.replaceRange('\n'+" ".repeat(level*2)+'- ', {line: line, ch: content.length});
|
||||
cm.setCursor({line: line+1, ch: 2+level*2});
|
||||
}else if(p = isNumberedList(content)){
|
||||
}else if(p = isTitle(content)){
|
||||
}
|
||||
},
|
||||
"Alt-Up": function(cm){
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(content)){
|
||||
}else if(p = isNumberedList(content)){
|
||||
}else if(p = isTitle(content)){
|
||||
}
|
||||
},
|
||||
"Alt-Down": function(cm){
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(content)){
|
||||
}else if(p = isNumberedList(content)){
|
||||
}else if(p = isTitle(content)){
|
||||
}
|
||||
},
|
||||
"Shift-Alt-Enter": function(cm){
|
||||
const line = cm.getCursor().line,
|
||||
content = cm.getLine(line);
|
||||
let p = null;
|
||||
|
||||
if(p = isItemList(content)){
|
||||
}else if(p = isNumberedList(content)){
|
||||
}else if(p = isTitle(content)){
|
||||
}
|
||||
},
|
||||
"Shift-Left": function(cm){
|
||||
const cycles = ["TODO", "", "DONE", "TODO"],
|
||||
line = cm.getCursor().line,
|
||||
content = cm.getLine(line),
|
||||
params = isTitle(content);
|
||||
|
||||
if(params === null) return;
|
||||
if(cycles.indexOf(params['status']) === -1) params['status'] = "";
|
||||
|
||||
params['status'] = cycles[cycles.indexOf(params['status']) + 1];
|
||||
cm.replaceRange(makeTitle(params), {line: line, ch: 0}, {line: line, ch: content.length});
|
||||
},
|
||||
"Shift-Right": function(cm){
|
||||
const cycles = ["TODO", "DONE", "", "TODO"],
|
||||
line = cm.getCursor().line,
|
||||
content = cm.getLine(line),
|
||||
params = isTitle(content);
|
||||
|
||||
if(params === null) return;
|
||||
if(cycles.indexOf(params['status']) === -1) params['status'] = "TODO";
|
||||
|
||||
params['status'] = cycles[cycles.indexOf(params['status']) + 1];
|
||||
cm.replaceRange(makeTitle(params), {line: line, ch: 0}, {line: line, ch: content.length});
|
||||
editor.addKeyMap({
|
||||
"Ctrl-X Ctrl-C": function(cm){
|
||||
cm.execCommand('quit');
|
||||
}
|
||||
});
|
||||
|
||||
function makeTitle(p){
|
||||
if(p['status'] === ""){
|
||||
return "*".repeat(p['level'])+" "+p['content'];
|
||||
}
|
||||
return "*".repeat(p['level'])+" "+p['status']+" "+p['content'];
|
||||
}
|
||||
function makeNumberedList(p){
|
||||
}
|
||||
function makeItemList(p){
|
||||
}
|
||||
|
||||
function previousOfType(cm, line){
|
||||
let content, tmp, i;
|
||||
for(i=line - 1, k=0; i>0; i--){
|
||||
content = cm.getLine(line);
|
||||
tmp = isItemList(content);
|
||||
if(tmp !== null) return ['list', tmp];
|
||||
tmp = isNumberedList(content);
|
||||
if(tmp !== null) return ['numbered', tmp];
|
||||
tmp = isTitle(content);
|
||||
if(tmp !== null) return ['title', tmp];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isItemList(content){
|
||||
if(content.trimLeft()[0] !== "-" || content.trimLeft()[1] !== " ") return null;
|
||||
const padding = content.replace(/^(\s*).*$/, "$1").length;
|
||||
if(padding % 2 !== 0) return null;
|
||||
return {
|
||||
level: padding / 2,
|
||||
content: content.trimLeft().replace(/^\s*\-\s(.*)$/, '$1')
|
||||
};
|
||||
}
|
||||
function isNumberedList(content){
|
||||
if(/^[0-9]+[\.\)]\s.*$/.test(content.trimLeft()) === false) return null;
|
||||
const padding = content.replace(/^(\s*)[0-9]+.*$/, "$1").length;
|
||||
if(padding % 2 !== 0) return null;
|
||||
return {
|
||||
level:padding / 2,
|
||||
n: parseInt(content.trimLeft().replace(/^([0-9]+).*$/, "$1")),
|
||||
content: content.trimLeft().replace(/^[0-9]+[\.\)]\s(.*)$/, '$1'),
|
||||
separator: content.trimLeft().replace(/^[0-9]+([\.\)]).*$/, '$1')
|
||||
};
|
||||
}
|
||||
function isTitle(content){
|
||||
if(/^\*+\s/.test(content) === false) return null;
|
||||
const match = content.match(/^(\*+)([\sA-Z]*)\s(.*)$/);
|
||||
if(match === null) return null;
|
||||
return {
|
||||
level: match[1].length,
|
||||
status: match[2].trim(),
|
||||
content: match[3]
|
||||
};
|
||||
}
|
||||
|
||||
function isEmpty(content){
|
||||
return content.trim() === "";
|
||||
}
|
||||
|
||||
function fold(cm, start){
|
||||
cm.foldCode(start, null, "fold");
|
||||
}
|
||||
function unfold(cm, start){
|
||||
cm.foldCode(start, null, "unfold");
|
||||
}
|
||||
function isFold(cm, start){
|
||||
const line = start.line;
|
||||
const marks = cm.findMarks(CodeMirror.Pos(line, 0), CodeMirror.Pos(line + 1, 0));
|
||||
for (let i = 0; i < marks.length; ++i)
|
||||
if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
|
||||
return false;
|
||||
}
|
||||
editor.on('touchstart', function(cm, e){
|
||||
setTimeout(() => {
|
||||
isFold(cm, cm.getCursor()) ? unfold(cm, cm.getCursor()) : fold(cm, cm.getCursor())
|
||||
}, 150);
|
||||
});
|
||||
|
||||
// fold everything except headers by default
|
||||
editor.operation(function() {
|
||||
for (var i = 0; i < editor.lineCount() ; i++) {
|
||||
@ -308,17 +132,8 @@ CodeMirror.afterInit = function(editor){
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
function collapseWidget(){
|
||||
let $widget = document.createElement('span');
|
||||
$widget.appendChild(document.createTextNode('colapse'));
|
||||
return $widget;
|
||||
}
|
||||
function expandWidget(){
|
||||
let $widget = document.createElement('span');
|
||||
$widget.appendChild(document.createTextNode('expand'));
|
||||
return $widget;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default CodeMirror;
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
|
||||
import { NgIf, Fab, Icon } from '../../components/';
|
||||
import { Editor } from './editor';
|
||||
import { MenuBar } from './menubar';
|
||||
import { OrgTodosViewer, OrgEventsViewer } from './org_viewer';
|
||||
|
||||
|
||||
import './ide.scss';
|
||||
|
||||
@ -11,20 +14,16 @@ export class IDE extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
event: new Subject(),
|
||||
contentToSave: props.content,
|
||||
needSaving: false
|
||||
needSaving: false,
|
||||
appear_agenda: false,
|
||||
appear_todo: false,
|
||||
mode: null,
|
||||
folding: null
|
||||
};
|
||||
}
|
||||
|
||||
onContentUpdate(text){
|
||||
if(this.props.content === text){
|
||||
this.props.needSavingUpdate(false);
|
||||
}else{
|
||||
this.props.needSavingUpdate(true);
|
||||
}
|
||||
this.setState({contentToSave: text})
|
||||
}
|
||||
|
||||
save(){
|
||||
if(this.props.needSaving === false) return;
|
||||
|
||||
@ -37,14 +36,69 @@ export class IDE extends React.Component {
|
||||
file = blob;
|
||||
}
|
||||
this.props.onSave(file)
|
||||
.then(() => this.props.needSavingUpdate(false))
|
||||
.then(() => this.props.needSavingUpdate(false));
|
||||
}
|
||||
|
||||
onUpdate(property, refresh, value){
|
||||
this.setState({ [property]: value }, () => {
|
||||
if(refresh){
|
||||
this.state.event.next(["refresh"]);
|
||||
}
|
||||
if(this.props.content === this.state.contentToSave){
|
||||
this.props.needSavingUpdate(false);
|
||||
}else{
|
||||
this.props.needSavingUpdate(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/* Org Viewer specific stuff */
|
||||
toggleAgenda(){
|
||||
this.setState({appear_agenda: !this.state.appear_agenda});
|
||||
}
|
||||
toggleTodo(){
|
||||
this.setState({appear_todo: !this.state.appear_todo});
|
||||
}
|
||||
onModeChange(){
|
||||
this.state.event.next(["fold"]);
|
||||
}
|
||||
goTo(lineNumber){
|
||||
this.state.event.next(["goTo", lineNumber]);
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div style={{height: '100%'}}>
|
||||
<MenuBar title={this.props.filename} download={this.props.url} />
|
||||
<Editor onSave={this.save.bind(this)} filename={this.props.filename} content={this.props.content} onChange={this.onContentUpdate.bind(this)} />
|
||||
<div style={{height: '100%'}} className="no-select">
|
||||
<MenuBar title={this.props.filename} download={this.props.url}>
|
||||
<NgIf type="inline" cond={this.state.mode === 'orgmode'}>
|
||||
<span onClick={this.onModeChange.bind(this)}>
|
||||
<NgIf cond={this.state.folding === "SHOW_ALL"} type="inline">
|
||||
<Icon name="arrow_up_double"/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.folding === "OVERVIEW"} type="inline">
|
||||
<Icon name="arrow_down"/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.folding === "CONTENT"} type="inline">
|
||||
<Icon name="arrow_down_double"/>
|
||||
</NgIf>
|
||||
</span>
|
||||
|
||||
<span onClick={this.toggleAgenda.bind(this)}>
|
||||
<Icon name="calendar_white"/>
|
||||
</span>
|
||||
<span onClick={this.toggleTodo.bind(this)}>
|
||||
<Icon name="todo_white"/>
|
||||
</span>
|
||||
</NgIf>
|
||||
</MenuBar>
|
||||
|
||||
<Editor onSave={this.save.bind(this)} filename={this.props.filename}
|
||||
content={this.state.contentToSave}
|
||||
event={this.state.event.asObservable()}
|
||||
onModeChange={this.onUpdate.bind(this, 'mode', false)}
|
||||
onFoldChange={this.onUpdate.bind(this, 'folding', false)}
|
||||
onChange={this.onUpdate.bind(this, 'contentToSave', false)} />
|
||||
|
||||
<ReactCSSTransitionGroup transitionName="fab" transitionLeave={true} transitionEnter={true} transitionAppear={true} transitionAppearTimeout={400} transitionEnterTimeout={400} transitionLeaveTimeout={200}>
|
||||
<NgIf key={this.props.needSaving} cond={this.props.needSaving}>
|
||||
@ -52,10 +106,17 @@ export class IDE extends React.Component {
|
||||
<Fab onClick={this.save.bind(this)}><Icon name="save" style={{height: '100%', width: '100%'}}/></Fab>
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.isSaving}>
|
||||
<Fab><Icon name="loading_white" style={{height: '100%', width: '100%'}}/></Fab>
|
||||
<Fab><Icon name="loading" style={{height: '100%', width: '100%'}}/></Fab>
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
</ReactCSSTransitionGroup>
|
||||
|
||||
<OrgEventsViewer isActive={this.state.appear_agenda} content={this.state.contentToSave}
|
||||
onUpdate={this.onUpdate.bind(this, "contentToSave", true)} goTo={this.goTo.bind(this)}
|
||||
onQuit={this.toggleAgenda.bind(this)} />
|
||||
<OrgTodosViewer isActive={this.state.appear_todo} content={this.state.contentToSave}
|
||||
onUpdate={this.onUpdate.bind(this, "contentToSave", true)} goTo={this.goTo.bind(this)}
|
||||
onQuit={this.toggleTodo.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@ -11,7 +11,12 @@ export const MenuBar = (props) => {
|
||||
<div className="component_menubar">
|
||||
<Container>
|
||||
<ReactCSSTransitionGroup transitionName="menubar" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={350}>
|
||||
<DownloadButton link={props.download} name={props.title} />
|
||||
<div className="action-item">
|
||||
<span className="specific">
|
||||
{props.children}
|
||||
</span>
|
||||
<DownloadButton link={props.download} name={props.title} />
|
||||
</div>
|
||||
<span style={{letterSpacing: '0.3px'}}>{props.title}</span>
|
||||
</ReactCSSTransitionGroup>
|
||||
</Container>
|
||||
@ -52,16 +57,16 @@ class DownloadButton extends React.Component {
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div style={{float: 'right', height: '1em'}}>
|
||||
<NgIf cond={!this.state.loading} style={{display: 'inline'}}>
|
||||
<span>
|
||||
<NgIf cond={!this.state.loading} type="inline">
|
||||
<a href={this.props.link} download={this.props.name} onClick={this.onDownloadRequest.bind(this)}>
|
||||
<Icon name="download" style={{width: '15px', height: '15px'}} />
|
||||
<Icon name="download_white" />
|
||||
</a>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.loading} style={{display: 'inline'}}>
|
||||
<Icon name="loading" style={{width: '15px', height: '15px'}} />
|
||||
<NgIf cond={this.state.loading} type="inline">
|
||||
<Icon name="loading_white" />
|
||||
</NgIf>
|
||||
</div>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,20 @@
|
||||
color: var(--bg-color);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
.action-item{
|
||||
float: right;
|
||||
.specific{ padding-right: 10px; }
|
||||
.component_icon{
|
||||
height: 17px;
|
||||
width: 17px;
|
||||
margin: 0px 5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
span:last-child .component_icon{
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
252
client/pages/viewerpage/org_viewer.js
Normal file
@ -0,0 +1,252 @@
|
||||
import React from 'react';
|
||||
import { StickyContainer, Sticky } from 'react-sticky';
|
||||
|
||||
import { Modal, Container, NgIf, Icon, Dropdown, DropdownButton, DropdownList, DropdownItem } from '../../components/';
|
||||
import { extractEvents, extractTodos } from '../../helpers/org';
|
||||
import './org_viewer.scss';
|
||||
|
||||
export const OrgEventsViewer = (props) => {
|
||||
if(props.isActive !== true) return null;
|
||||
const headlines = extractEvents(props.content);
|
||||
|
||||
return (
|
||||
<OrgViewer title="Agenda" headlines={headlines} content={props.content} isActive={props.isActive}
|
||||
onQuit={props.onQuit} goTo={props.goTo} onUpdate={props.onUpdate} />
|
||||
);
|
||||
};
|
||||
|
||||
export const OrgTodosViewer = (props) => {
|
||||
if(props.isActive !== true) return null;
|
||||
const headlines = extractTodos(props.content);
|
||||
|
||||
return (
|
||||
<OrgViewer title="Todos" headlines={headlines} content={props.content} isActive={props.isActive}
|
||||
onQuit={props.onQuit} goTo={props.goTo} onUpdate={props.onUpdate} />
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
class OrgViewer extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
headlines: this.buildHeadlines(props.headlines)
|
||||
};
|
||||
}
|
||||
|
||||
componentWillReceiveProps(props){
|
||||
this.setState({
|
||||
headlines: this.buildHeadlines(props.headlines)
|
||||
});
|
||||
}
|
||||
|
||||
buildHeadlines(headlines){
|
||||
return headlines
|
||||
.reduce((acc, headline) => {
|
||||
if(!acc[headline['key']]){ acc[headline['key']] = []; }
|
||||
acc[headline['key']].push(headline);
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
|
||||
onChange(i, j, state){
|
||||
this.state.headlines[Object.keys(this.state.headlines)[i]][j].status = state;
|
||||
|
||||
this.setState({
|
||||
headlines: this.state.headlines
|
||||
});
|
||||
}
|
||||
|
||||
navigate(line){
|
||||
this.props.onQuit();
|
||||
this.props.goTo(line);
|
||||
}
|
||||
|
||||
|
||||
onTaskUpdate(type, line, value){
|
||||
const content = this.props.content.split("\n");
|
||||
switch(type){
|
||||
case "status":
|
||||
const [status, state] = value;
|
||||
let next = "DONE";
|
||||
if(status === "DONE") next = "TODO";
|
||||
content[line] = content[line].replace(status, next);
|
||||
break;
|
||||
case "subtask":
|
||||
if(value === "DONE"){
|
||||
content[line] = content[line].replace(/\[.\]/, '[X]');
|
||||
}else{
|
||||
content[line] = content[line].replace(/\[.\]/, '[ ]');
|
||||
}
|
||||
break;
|
||||
case "schedule":
|
||||
break;
|
||||
case "deadline":
|
||||
break;
|
||||
};
|
||||
this.props.onUpdate(content.join("\n"));
|
||||
}
|
||||
|
||||
|
||||
render(){
|
||||
return (
|
||||
<Modal className="todo-modal" isActive={this.props.isActive} onQuit={this.props.onQuit}>
|
||||
<div className="modal-top no-select">
|
||||
<span onClick={this.props.onQuit}>
|
||||
<Icon name="close"/>
|
||||
</span>
|
||||
<h1>{this.props.title}</h1>
|
||||
</div>
|
||||
<NgIf cond={this.props.headlines.length === 0} className="nothing">
|
||||
<div className="big">Nothing</div>
|
||||
<div className="contrib">
|
||||
If you got some free time, help us making Nuage better by <a href="https://github.com/mickael-kerjean/nuage">contributing</a>? ;)
|
||||
</div>
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.headlines.length > 0}>
|
||||
<StickyContainer className="container" style={{height: window.innerHeight > 650 ? 500 : window.innerHeight - 150, overflowY: "auto", padding: "0 10px"}}>
|
||||
{
|
||||
Object.keys(this.state.headlines).map((list, i) => {
|
||||
return (
|
||||
<div key={i}>
|
||||
<Sticky relative>
|
||||
{
|
||||
({isSticky, wasSticky, style, distanceFromTop, distanceFromBottom, calculatedHeight}) => {
|
||||
return (
|
||||
<div className="sticky_header no-select" style={{...style, overflow: "auto", background: "white"}}>
|
||||
<h2>{list} <span>{this.state.headlines[list].length}</span></h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
</Sticky>
|
||||
<div className="list">
|
||||
{
|
||||
this.state.headlines[list].map((headline, j) => {
|
||||
return (
|
||||
<Headline
|
||||
type={this.props.title.toLowerCase()}
|
||||
onTaskUpdate={this.onTaskUpdate.bind(this)}
|
||||
onChange={this.onChange.bind(this, i, j)}
|
||||
tasks={headline.tasks}
|
||||
key={j} title={headline.title}
|
||||
tags={headline.tags}
|
||||
line={headline.line}
|
||||
status={headline.status || null}
|
||||
todo_status={headline.todo_status}
|
||||
goTo={this.navigate.bind(this, headline.line)} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</StickyContainer>
|
||||
</NgIf>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class Headline extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
properties: false
|
||||
};
|
||||
}
|
||||
|
||||
onMenuAction(key, value){
|
||||
if(key === "navigate"){
|
||||
this.props.goTo();
|
||||
}else if(key === "properties"){
|
||||
this.setState({properties: !this.state.properties});
|
||||
}
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="component_headline">
|
||||
<div className={"no-select headline-main "+this.props.todo_status + " " +(this.props.is_overdue === true ? "overdue" : "")}>
|
||||
<div className="title" onClick={this.props.onTaskUpdate.bind(this, 'status', this.props.line, [this.props.status, this.props.todo_status])}>
|
||||
<div>
|
||||
<span>{this.props.title}</span>
|
||||
<div className="tags">
|
||||
{
|
||||
this.props.tags.map((tag, i) => {
|
||||
return (
|
||||
<span className="tag" key={i}>{tag}</span>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Dropdown onChange={this.onMenuAction.bind(this)}>
|
||||
<DropdownButton>
|
||||
<Icon name="more" />
|
||||
</DropdownButton>
|
||||
<DropdownList>
|
||||
<DropdownItem name="navigate" icon="arrow_right"> Navigate </DropdownItem>
|
||||
</DropdownList>
|
||||
</Dropdown>
|
||||
</div>
|
||||
<NgIf className="headline-properties" cond={this.state.properties}>
|
||||
<div>
|
||||
<label> <Icon name="schedule" />
|
||||
<input type="date" onChange={(e) => this.props.onTaskUpdate.bind(this, 'schedule', this.props.line, e.target.value)}/>
|
||||
</label>
|
||||
</div>
|
||||
<div>
|
||||
<label> <Icon name="deadline" />
|
||||
<input type="date" onChange={(e) => this.props.onTaskUpdate.bind(this, 'deadline', this.props.line, e.target.value)}/>
|
||||
</label>
|
||||
</div>
|
||||
</NgIf>
|
||||
<NgIf cond={this.props.tasks.length > 0 && this.props.todo_status === "todo" && this.props.type === 'todos'} className="subtask_container">
|
||||
{
|
||||
this.props.tasks.map((task, i) => {
|
||||
return (
|
||||
<Subtask key={i} label={task.title} status={task.status}
|
||||
onStatusChange={this.props.onTaskUpdate.bind(this, 'subtask', task.line)} />
|
||||
);
|
||||
})
|
||||
}
|
||||
</NgIf>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Subtask extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = this.calculateState();
|
||||
}
|
||||
|
||||
calculateState(){
|
||||
return {checked: this.props.status === "DONE"};
|
||||
}
|
||||
|
||||
updateState(e){
|
||||
this.props.onStatusChange(e.target.checked ? "DONE" : "TODO");
|
||||
this.setState({checked: e.target.checked});
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div className="component_subtask no-select">
|
||||
<label>
|
||||
<input type="checkbox" checked={this.props.status === "DONE"}
|
||||
onChange={this.updateState.bind(this)} />
|
||||
<span>{this.props.label}</span>
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
197
client/pages/viewerpage/org_viewer.scss
Normal file
@ -0,0 +1,197 @@
|
||||
.component_modal.todo-modal{
|
||||
background: var(--super-light);
|
||||
background: linear-gradient(var(--super-light), var(--bg-color));
|
||||
|
||||
> div{
|
||||
width: 90%;
|
||||
max-width: 500px;
|
||||
padding: 5px;
|
||||
border-bottom-right-radius: 20px;
|
||||
box-shadow: 1px 2px 1px rgba(0,00,0,0.05);
|
||||
}
|
||||
.modal-top{
|
||||
margin: -5px -5px 0 -5px;
|
||||
padding: 30px 20px;
|
||||
background: var(--primary);
|
||||
border-bottom: 1px solid var(--emphasis-primary);
|
||||
color: rgba(0,0,0,0.5);
|
||||
h1{margin: 0;}
|
||||
span{
|
||||
float: right;
|
||||
margin-top: -10px;
|
||||
.component_icon{
|
||||
padding: 2px 0;
|
||||
height: 22px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nothing{
|
||||
color: var(--light);
|
||||
.big{
|
||||
text-align: center;
|
||||
padding: 40px 0;
|
||||
font-size: 1.3em;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
a{color: var(--primary);}
|
||||
}
|
||||
|
||||
.container{
|
||||
margin: 10px 10px 10px 0px;
|
||||
|
||||
.sticky_header{
|
||||
z-index: 2;
|
||||
border-bottom: 2px solid var(--primary);
|
||||
h2{
|
||||
text-transform: uppercase;
|
||||
font-size: 1.1em;
|
||||
color: var(--primary);
|
||||
margin: 10px 0 15px 0;
|
||||
font-weight: normal;
|
||||
span{
|
||||
background: var(--bg-color);
|
||||
font-size: 0.6em;
|
||||
color: var(--emphasis-secondary);
|
||||
float: right;
|
||||
line-height: 1em;
|
||||
vertical-align: middle;
|
||||
display: inline-block;
|
||||
padding: 3px 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.list{
|
||||
margin: 0px 0 40px 0;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.all-done p{
|
||||
font-size: 1.3em;
|
||||
padding: 10px 10px 10px 20px;
|
||||
color: var(--light);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
.component_headline{
|
||||
.headline-main{
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
border-top: 2px solid var(--super-light);
|
||||
border-bottom: 2px solid var(--super-light);
|
||||
margin-bottom: -2px;
|
||||
padding: 5px 0;
|
||||
|
||||
&.overdue{color: var(--error);}
|
||||
&.done{
|
||||
color: var(--light);
|
||||
&:hover{background: inherit;}
|
||||
transition: color 0.35s ease-out;
|
||||
}
|
||||
|
||||
.title{
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
line-height: 32px;
|
||||
> div{
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.tags{
|
||||
display: inline-block;
|
||||
margin-left: 10px;
|
||||
font-size: 0.9em;
|
||||
.tag{
|
||||
padding: 0 5px;
|
||||
margin: 0 3px;
|
||||
color: red;
|
||||
background: var(--bg-color);
|
||||
color: var(--light);
|
||||
border-radius: 4px;
|
||||
&:before{
|
||||
content: '#';
|
||||
color: rgba(0,0,0,0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.component_icon{
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
|
||||
transition: color 0.1s ease-out, border-color 0.2s ease-out;
|
||||
.title{
|
||||
position: relative;
|
||||
&:after{
|
||||
content: ' ';
|
||||
display: block;
|
||||
width: 100%;
|
||||
border-top: 2px solid var(--super-light);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: -7px;
|
||||
transition: bottom 0.15s ease-out;
|
||||
}
|
||||
}
|
||||
&.done{
|
||||
transition: border-color 0.15s ease-out;
|
||||
border-color: rgba(0,0,0,0);
|
||||
.title:after{
|
||||
bottom: 15px;
|
||||
transition: bottom 0.25s ease-out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.headline-properties{
|
||||
> div{display: inline-block;}
|
||||
input[type="date"] {
|
||||
background: inherit;
|
||||
border: none;
|
||||
font-size: inherit;
|
||||
font-family: inherit;
|
||||
color: inherit;
|
||||
}
|
||||
.component_icon{
|
||||
height: 20px;
|
||||
float: left;
|
||||
margin-right: 5px;
|
||||
}
|
||||
background: var(--super-light);
|
||||
color: var(--light);
|
||||
font-size: 0.95em;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.subtask_container{
|
||||
background: #fafafa;
|
||||
color: var(--light);
|
||||
font-size: 0.95em;
|
||||
line-height: 0.95em;
|
||||
padding: 5px 0;
|
||||
|
||||
.component_subtask{
|
||||
margin: 5px;
|
||||
label{
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
input[type="checkbox"]{ margin: 0 5px; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -29,6 +29,7 @@
|
||||
"node-ssh": "^4.2.2",
|
||||
"nodegit": "^0.18.3",
|
||||
"path": "^0.12.7",
|
||||
"react-sticky": "^6.0.2",
|
||||
"request": "^2.81.0",
|
||||
"request-promise": "^4.2.1",
|
||||
"scp2": "^0.5.0",
|
||||
@ -72,6 +73,7 @@
|
||||
"mocha": "^5.0.4",
|
||||
"node-sass": "^4.7.2",
|
||||
"nodemon": "^1.17.1",
|
||||
"org": "https://github.com/mickael-kerjean/org-js/tarball/master",
|
||||
"prop-types": "^15.5.10",
|
||||
"react": "^15.3.2",
|
||||
"react-addons-css-transition-group": "^15.6.2",
|
||||
|
||||