Merge branch 'develop' into 'master'
Redesign and Feature updates Closes #111, #117, #120, #119, #121, #128, and #116 See merge request b0/spectral!43
This commit is contained in:
commit
0473f25d38
|
@ -3,13 +3,13 @@ image: Visual Studio 2017
|
||||||
environment:
|
environment:
|
||||||
DEPLOY_DIR: Spectral-%APPVEYOR_BUILD_VERSION%
|
DEPLOY_DIR: Spectral-%APPVEYOR_BUILD_VERSION%
|
||||||
matrix:
|
matrix:
|
||||||
- QTDIR: C:\Qt\5.11\msvc2017_64
|
- QTDIR: C:\Qt\5.12.1\msvc2017_64
|
||||||
VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
|
VCVARS: "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\Community\\VC\\Auxiliary\\Build\\vcvars64.bat"
|
||||||
PLATFORM:
|
PLATFORM:
|
||||||
|
|
||||||
init:
|
init:
|
||||||
- call "%QTDIR%\bin\qtenv2.bat"
|
- call "%QTDIR%\bin\qtenv2.bat"
|
||||||
- set PATH=C:\Qt\Tools\QtCreator\bin;%PATH%
|
- set PATH=%PATH%;C:\Qt\Tools\QtCreator\bin
|
||||||
- call "%VCVARS%" %platform%
|
- call "%VCVARS%" %platform%
|
||||||
- cd /D "%APPVEYOR_BUILD_FOLDER%"
|
- cd /D "%APPVEYOR_BUILD_FOLDER%"
|
||||||
|
|
||||||
|
@ -17,14 +17,14 @@ before_build:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
|
|
||||||
build_script:
|
build_script:
|
||||||
- qmake spectral.pro CONFIG+=debug CONFIG+=qml_debug PREFIX="%DEPLOY_DIR%"
|
- qmake spectral.pro CONFIG+=release CONFIG+=qtquickcompiler PREFIX="%DEPLOY_DIR%"
|
||||||
- nmake
|
- nmake
|
||||||
|
|
||||||
after_build:
|
after_build:
|
||||||
- nmake install
|
- nmake install
|
||||||
- windeployqt --debug --qmldir qml --qmldir imports "%DEPLOY_DIR%\spectral.exe"
|
- windeployqt --release --qmldir qml --qmldir imports "%DEPLOY_DIR%\spectral.exe"
|
||||||
- 7z a spectral.zip "%DEPLOY_DIR%\"
|
- 7z a spectral.zip "%DEPLOY_DIR%\"
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
- path: spectral.zip
|
- path: spectral.zip
|
||||||
name: portable
|
name: portable
|
||||||
|
|
|
@ -22,11 +22,11 @@ build-appimage:
|
||||||
before_script:
|
before_script:
|
||||||
- git submodule update --init --recursive
|
- git submodule update --init --recursive
|
||||||
script:
|
script:
|
||||||
- /opt/qt511/bin/qt511-env.sh
|
- /opt/qt512/bin/qt512-env.sh
|
||||||
- /opt/qt511/bin/qmake CONFIG+=debug CONFIG+=qml_debug PREFIX=/usr
|
- /opt/qt512/bin/qmake CONFIG+=debug CONFIG+=qml_debug PREFIX=/usr
|
||||||
- make
|
- make
|
||||||
- make INSTALL_ROOT=appdir install
|
- make INSTALL_ROOT=appdir install
|
||||||
- /usr/bin/linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/org.eu.encom.spectral.desktop -appimage -qmldir=qml -qmldir=imports -qmake=/opt/qt511/bin/qmake
|
- /usr/bin/linuxdeployqt-continuous-x86_64.AppImage appdir/usr/share/applications/org.eu.encom.spectral.desktop -appimage -qmldir=qml -qmldir=imports -qmake=/opt/qt512/bin/qmake
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- Spectral-x86_64.AppImage
|
- Spectral*.AppImage
|
||||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 11 KiB |
Binary file not shown.
Before Width: | Height: | Size: 697 KiB |
|
@ -0,0 +1,29 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 13.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 14576) -->
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="793.322px" height="340.809px" viewBox="0 0 793.322 340.809" enable-background="new 0 0 793.322 340.809" xml:space="preserve">
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M34.004,340.809H2c-1.104,0-2-0.896-2-2V2c0-1.104,0.896-2,2-2h32.004c1.104,0,2,0.896,2,2 v7.71c0,1.104-0.896,2-2,2h-21.13v317.386h21.13c1.104,0,2,0.896,2,2.001v7.712C36.004,339.913,35.108,340.809,34.004,340.809 L34.004,340.809z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M10.875,9.711v321.386h23.13v7.711H1.999V2.001h32.006v7.71H10.875z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M252.402,233.711h-32.993c-1.104,0-2-0.896-2-2v-68.073c0-3.949-0.154-7.722-0.457-11.213 c-0.289-3.282-1.074-6.153-2.332-8.53c-1.204-2.276-3.017-4.119-5.384-5.476c-2.393-1.362-5.775-2.056-10.042-2.056 c-4.238,0-7.674,0.798-10.213,2.371c-2.565,1.596-4.604,3.701-6.053,6.258c-1.498,2.643-2.51,5.694-3.013,9.067 c-0.526,3.513-0.793,7.125-0.793,10.741v66.91c0,1.104-0.896,2-2,2h-32.991c-1.104,0-2-0.896-2-2v-67.373 c0-3.435-0.078-6.964-0.228-10.485c-0.148-3.251-0.767-6.278-1.841-8.995c-1.018-2.571-2.667-4.584-5.047-6.153 c-2.372-1.552-6.029-2.341-10.865-2.341c-1.372,0-3.265,0.328-5.629,0.976c-2.28,0.624-4.536,1.826-6.705,3.577 c-2.152,1.732-4.036,4.306-5.605,7.655c-1.569,3.356-2.367,7.877-2.367,13.438v69.701c0,1.104-0.895,2-2,2H68.857 c-1.104,0-2-0.896-2-2V111.594c0-1.104,0.896-1.999,2-1.999h31.13c1.104,0,2,0.896,2,1.999v11.007 c3.834-4.499,8.248-8.152,13.173-10.896c6.396-3.559,13.799-5.362,22.002-5.362c7.846,0,15.127,1.548,21.642,4.604 c5.794,2.722,10.424,7.26,13.791,13.52c3.449-4.362,7.833-8.306,13.071-11.752c6.422-4.228,14.102-6.371,22.824-6.371 c6.499,0,12.625,0.807,18.209,2.399c5.686,1.628,10.635,4.271,14.712,7.857c4.088,3.605,7.318,8.357,9.601,14.123 c2.25,5.719,3.391,12.649,3.391,20.604v80.384C254.402,232.815,253.507,233.711,252.402,233.711L252.402,233.711z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M99.988,111.595v16.264h0.463c4.338-6.191,9.563-10.998,15.684-14.406 c6.117-3.402,13.129-5.11,21.027-5.11c7.588,0,14.521,1.475,20.793,4.415c6.274,2.945,11.038,8.131,14.291,15.567 c3.56-5.265,8.4-9.913,14.521-13.94c6.117-4.025,13.358-6.042,21.724-6.042c6.351,0,12.234,0.776,17.66,2.325 c5.418,1.549,10.065,4.027,13.938,7.434c3.869,3.41,6.889,7.863,9.062,13.357c2.167,5.504,3.253,12.122,3.253,19.869v80.385H219.41 v-68.074c0-4.025-0.154-7.82-0.465-11.385c-0.313-3.56-1.161-6.656-2.555-9.293c-1.395-2.631-3.45-4.724-6.157-6.274 c-2.711-1.543-6.391-2.322-11.037-2.322s-8.403,0.896-11.269,2.671c-2.868,1.784-5.112,4.109-6.737,6.971 c-1.626,2.869-2.711,6.12-3.252,9.762c-0.545,3.638-0.814,7.318-0.814,11.035v66.91h-32.991v-67.375c0-3.562-0.081-7.087-0.23-10.57 c-0.158-3.487-0.814-6.7-1.978-9.645c-1.162-2.94-3.099-5.304-5.809-7.088c-2.711-1.775-6.699-2.671-11.965-2.671 c-1.551,0-3.603,0.349-6.156,1.048c-2.556,0.697-5.036,2.016-7.435,3.949c-2.404,1.938-4.454,4.726-6.158,8.363 c-1.705,3.642-2.556,8.402-2.556,14.287v69.701h-32.99V111.595H99.988z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M304.909,236.733c-5.883,0-11.46-0.729-16.574-2.163c-5.192-1.464-9.806-3.774-13.713-6.871 c-3.944-3.117-7.068-7.111-9.282-11.871c-2.205-4.733-3.324-10.412-3.324-16.876c0-7.13,1.293-13.117,3.846-17.797 c2.542-4.674,5.877-8.464,9.912-11.263c3.97-2.752,8.556-4.842,13.63-6.209c4.901-1.322,9.937-2.394,14.961-3.184 c4.986-0.775,9.949-1.404,14.754-1.872c4.679-0.452,8.88-1.139,12.489-2.039c3.412-0.854,6.118-2.09,8.042-3.672 c1.666-1.37,2.416-3.384,2.292-6.151c-0.002-3.289-0.502-5.816-1.492-7.595c-0.998-1.798-2.283-3.15-3.927-4.138 c-1.703-1.02-3.725-1.713-6.012-2.062c-2.47-0.37-5.146-0.557-7.947-0.557c-6.034,0-10.789,1.271-14.135,3.783 c-3.233,2.424-5.155,6.64-5.714,12.527c-0.098,1.026-0.961,1.812-1.992,1.812h-32.992c-0.552,0-1.079-0.229-1.457-0.629 c-0.376-0.402-0.572-0.941-0.54-1.491c0.485-8.073,2.55-14.894,6.142-20.272c3.548-5.331,8.147-9.682,13.661-12.931 c5.424-3.191,11.612-5.498,18.392-6.857c6.684-1.335,13.5-2.013,20.26-2.013c6.096,0,12.365,0.437,18.626,1.296 c6.377,0.88,12.285,2.622,17.562,5.177c5.376,2.604,9.845,6.29,13.282,10.951c3.498,4.744,5.271,11.048,5.271,18.731v62.494 c0,5.307,0.306,10.462,0.915,15.319c0.576,4.64,1.572,8.116,2.963,10.338c0.385,0.616,0.407,1.395,0.055,2.031 c-0.353,0.635-1.022,1.03-1.75,1.03h-33.457c-0.861,0-1.624-0.55-1.898-1.367c-0.646-1.941-1.176-3.939-1.572-5.936 c-0.141-0.696-0.267-1.402-0.38-2.12c-4.825,4.184-10.349,7.24-16.474,9.105C320.033,235.609,312.489,236.733,304.909,236.733 L304.909,236.733z M341.941,176.661c-0.809,0.409-1.676,0.768-2.596,1.074c-2.161,0.72-4.511,1.326-6.988,1.807 c-2.442,0.475-5.033,0.872-7.699,1.186c-2.631,0.311-5.251,0.697-7.784,1.146c-2.329,0.433-4.705,1.035-7.051,1.792 c-2.194,0.711-4.114,1.667-5.699,2.842c-1.531,1.128-2.785,2.587-3.731,4.335c-0.917,1.709-1.385,3.97-1.385,6.719 c0,2.598,0.465,4.778,1.385,6.481c0.928,1.722,2.142,3.035,3.716,4.018c1.644,1.026,3.601,1.757,5.816,2.17 c2.344,0.439,4.799,0.663,7.297,0.663c6.105,0,10.836-0.996,14.063-2.961c3.244-1.973,5.666-4.349,7.199-7.062 c1.568-2.78,2.542-5.62,2.892-8.436c0.376-3.019,0.565-5.436,0.565-7.187V176.661L341.941,176.661z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M273.544,129.255c3.405-5.113,7.744-9.215,13.012-12.316 c5.264-3.097,11.186-5.303,17.771-6.621c6.582-1.315,13.205-1.976,19.865-1.976c6.042,0,12.158,0.428,18.354,1.277 c6.195,0.855,11.85,2.522,16.962,4.997c5.111,2.477,9.292,5.926,12.546,10.338c3.253,4.414,4.879,10.262,4.879,17.543v62.494 c0,5.428,0.31,10.611,0.931,15.567c0.615,4.959,1.701,8.676,3.251,11.153H347.66c-0.621-1.86-1.126-3.755-1.511-5.693 c-0.39-1.933-0.661-3.908-0.813-5.923c-5.267,5.422-11.465,9.217-18.585,11.386c-7.127,2.163-14.407,3.251-21.842,3.251 c-5.733,0-11.077-0.698-16.033-2.09c-4.958-1.395-9.293-3.562-13.01-6.51c-3.718-2.938-6.622-6.656-8.713-11.147 s-3.138-9.84-3.138-16.033c0-6.813,1.199-12.43,3.604-16.84c2.399-4.417,5.495-7.939,9.295-10.575 c3.793-2.632,8.129-4.607,13.01-5.923c4.878-1.315,9.795-2.358,14.752-3.137c4.957-0.772,9.835-1.393,14.638-1.857 c4.801-0.466,9.062-1.164,12.779-2.093c3.718-0.929,6.658-2.282,8.829-4.065c2.165-1.781,3.172-4.375,3.02-7.785 c0-3.56-0.58-6.389-1.742-8.479c-1.161-2.09-2.711-3.719-4.646-4.88c-1.937-1.161-4.183-1.936-6.737-2.325 c-2.557-0.382-5.309-0.58-8.248-0.58c-6.506,0-11.617,1.395-15.335,4.183c-3.716,2.788-5.889,7.437-6.506,13.94h-32.991 C268.199,140.794,270.132,134.363,273.544,129.255z M338.713,175.838c-2.09,0.696-4.337,1.275-6.736,1.741 c-2.402,0.465-4.918,0.853-7.551,1.161c-2.635,0.313-5.268,0.698-7.899,1.163c-2.48,0.461-4.919,1.086-7.317,1.857 c-2.404,0.779-4.495,1.822-6.274,3.138c-1.784,1.317-3.216,2.985-4.3,4.994c-1.085,2.014-1.626,4.571-1.626,7.668 c0,2.94,0.541,5.422,1.626,7.431c1.084,2.017,2.558,3.604,4.416,4.765s4.025,1.976,6.507,2.438c2.475,0.466,5.031,0.698,7.665,0.698 c6.505,0,11.537-1.082,15.103-3.253c3.561-2.166,6.192-4.762,7.899-7.785c1.702-3.019,2.749-6.072,3.137-9.174 c0.384-3.097,0.58-5.576,0.58-7.434v-12.316C342.547,174.173,340.805,175.14,338.713,175.838z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M444.542,234.874c-5.187,0-10.173-0.361-14.823-1.069c-4.802-0.732-9.104-2.183-12.779-4.313 c-3.789-2.185-6.821-5.341-9.006-9.375c-2.163-3.986-3.26-9.232-3.26-15.59v-68.859h-17.981c-1.104,0-2-0.896-2-1.999v-22.073 c0-1.104,0.896-1.999,2-1.999h17.981V75.582c0-1.104,0.896-2,2-2h32.992c1.104,0,2,0.896,2,2v34.014h22.162c1.104,0,2,0.896,2,1.999 v22.073c0,1.104-0.896,1.999-2,1.999h-22.162v57.479c0,6.229,1.198,8.731,2.202,9.733c1.004,1.007,3.506,2.205,9.738,2.205 c1.804,0,3.542-0.076,5.161-0.225c1.604-0.144,3.174-0.367,4.669-0.665c0.13-0.026,0.261-0.039,0.391-0.039 c0.458,0,0.907,0.159,1.27,0.454c0.463,0.379,0.73,0.946,0.73,1.546v25.555c0,0.979-0.707,1.813-1.672,1.974 c-2.834,0.472-6.041,0.794-9.527,0.957C451.015,234.798,447.718,234.874,444.542,234.874L444.542,234.874z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M463.825,111.595v22.072h-24.161v59.479c0,5.573,0.928,9.292,2.788,11.149 c1.856,1.859,5.576,2.788,11.152,2.788c1.859,0,3.638-0.076,5.343-0.232c1.703-0.152,3.33-0.388,4.878-0.696v25.557 c-2.788,0.465-5.887,0.773-9.293,0.931c-3.407,0.149-6.737,0.23-9.99,0.23c-5.111,0-9.953-0.35-14.521-1.048 c-4.571-0.695-8.597-2.047-12.081-4.063c-3.486-2.011-6.236-4.88-8.248-8.597c-2.016-3.714-3.021-8.595-3.021-14.639v-70.859h-19.98 v-22.072h19.98V75.583h32.992v36.012H463.825z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M512.613,233.711h-32.991c-1.104,0-2-0.896-2-2V111.594c0-1.104,0.896-1.999,2-1.999h31.366 c1.104,0,2,0.896,2,1.999v15.069c0.967-1.516,2.034-2.978,3.199-4.382c2.754-3.312,5.949-6.182,9.496-8.522 c3.545-2.332,7.385-4.169,11.415-5.462c4.056-1.298,8.327-1.954,12.691-1.954c2.341,0,4.953,0.418,7.766,1.243 c0.852,0.25,1.437,1.032,1.437,1.92v30.67c0,0.6-0.269,1.167-0.732,1.547c-0.361,0.296-0.808,0.452-1.265,0.452 c-0.133,0-0.265-0.013-0.398-0.039c-1.484-0.3-3.299-0.565-5.392-0.787c-2.098-0.224-4.136-0.339-6.062-0.339 c-5.706,0-10.572,0.95-14.467,2.823c-3.862,1.86-7.012,4.428-9.361,7.629c-2.389,3.263-4.115,7.12-5.127,11.47 c-1.043,4.479-1.574,9.409-1.574,14.647v54.132C514.613,232.815,513.717,233.711,512.613,233.711L512.613,233.711z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M510.988,111.595V133.9h0.465c1.546-3.72,3.636-7.163,6.272-10.341 c2.634-3.172,5.652-5.885,9.06-8.131c3.405-2.242,7.047-3.985,10.923-5.228c3.868-1.237,7.898-1.859,12.081-1.859 c2.168,0,4.566,0.39,7.202,1.163v30.67c-1.551-0.312-3.41-0.584-5.576-0.814c-2.17-0.233-4.26-0.35-6.274-0.35 c-6.041,0-11.152,1.01-15.332,3.021c-4.182,2.014-7.55,4.761-10.107,8.247c-2.555,3.487-4.379,7.55-5.462,12.198 c-1.083,4.645-1.625,9.682-1.625,15.102v54.133h-32.991V111.595H510.988z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M603.923,233.711H570.93c-1.104,0-2-0.896-2-2V111.594c0-1.104,0.896-1.999,2-1.999h32.994 c1.104,0,2,0.896,2,1.999v120.117C605.923,232.815,605.027,233.711,603.923,233.711L603.923,233.711z M603.923,95.006H570.93 c-1.104,0-2-0.896-2-1.999V65.825c0-1.104,0.896-2,2-2h32.994c1.104,0,2,0.896,2,2v27.182 C605.923,94.11,605.027,95.006,603.923,95.006L603.923,95.006z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M570.93,93.007V65.824h32.994v27.183H570.93z M603.924,111.595v120.117H570.93V111.595 H603.924z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M742.163,233.711h-37.64c-0.671,0-1.297-0.335-1.667-0.896l-23.426-35.352l-23.426,35.352 c-0.369,0.561-0.995,0.896-1.667,0.896h-36.938c-0.741,0-1.424-0.411-1.77-1.067c-0.345-0.654-0.3-1.449,0.118-2.061l42.435-62.055 l-38.71-55.793c-0.424-0.613-0.474-1.408-0.128-2.069c0.343-0.658,1.028-1.071,1.771-1.071h37.636c0.665,0,1.287,0.33,1.658,0.882 l19.477,28.893l19.255-28.884c0.372-0.556,0.996-0.891,1.665-0.891h36.475c0.746,0,1.43,0.415,1.776,1.078 c0.343,0.66,0.289,1.46-0.139,2.071l-38.69,55.082l43.578,62.744c0.424,0.61,0.474,1.408,0.128,2.066 C743.591,233.298,742.908,233.711,742.163,233.711L742.163,233.711z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M621.115,111.595h37.637l21.144,31.365l20.911-31.365h36.476l-39.496,56.226l44.377,63.892 h-37.64l-25.093-37.87l-25.094,37.87h-36.938l43.213-63.193L621.115,111.595z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M791.322,340.809h-32.008c-1.105,0-2-0.896-2-2v-7.712c0-1.105,0.896-2.001,2-2.001h21.13 V11.71h-21.13c-1.105,0-2-0.896-2-2V2c0-1.104,0.896-2,2-2h32.008c1.104,0,2,0.896,2,2v336.809 C793.322,339.913,792.426,340.809,791.322,340.809L791.322,340.809z"/>
|
||||||
|
<path opacity="0.5" fill="#FFFFFF" d="M782.443,331.097V9.711h-23.13v-7.71h32.008v336.807h-32.008v-7.711H782.443z"/>
|
||||||
|
<path d="M10.875,9.711v321.386h23.13v7.711H1.999V2.001h32.006v7.71H10.875z"/>
|
||||||
|
<path d="M99.988,111.595v16.264h0.463c4.338-6.191,9.563-10.998,15.684-14.406c6.117-3.402,13.129-5.11,21.027-5.11 c7.588,0,14.521,1.475,20.793,4.415c6.274,2.945,11.038,8.131,14.291,15.567c3.56-5.265,8.4-9.913,14.521-13.94 c6.117-4.025,13.358-6.042,21.724-6.042c6.351,0,12.234,0.776,17.66,2.325c5.418,1.549,10.065,4.027,13.938,7.434 c3.869,3.41,6.889,7.863,9.062,13.357c2.167,5.504,3.253,12.122,3.253,19.869v80.385H219.41v-68.074 c0-4.025-0.154-7.82-0.465-11.385c-0.313-3.56-1.161-6.656-2.555-9.293c-1.395-2.631-3.45-4.724-6.157-6.274 c-2.711-1.543-6.391-2.322-11.037-2.322s-8.403,0.896-11.269,2.671c-2.868,1.784-5.112,4.109-6.737,6.971 c-1.626,2.869-2.711,6.12-3.252,9.762c-0.545,3.638-0.814,7.318-0.814,11.035v66.91h-32.991v-67.375c0-3.562-0.081-7.087-0.23-10.57 c-0.158-3.487-0.814-6.7-1.978-9.645c-1.162-2.94-3.099-5.304-5.809-7.088c-2.711-1.775-6.699-2.671-11.965-2.671 c-1.551,0-3.603,0.349-6.156,1.048c-2.556,0.697-5.036,2.016-7.435,3.949c-2.404,1.938-4.454,4.726-6.158,8.363 c-1.705,3.642-2.556,8.402-2.556,14.287v69.701h-32.99V111.595H99.988z"/>
|
||||||
|
<path d="M273.544,129.255c3.405-5.113,7.744-9.215,13.012-12.316c5.264-3.097,11.186-5.303,17.771-6.621 c6.582-1.315,13.205-1.976,19.865-1.976c6.042,0,12.158,0.428,18.354,1.277c6.195,0.855,11.85,2.522,16.962,4.997 c5.111,2.477,9.292,5.926,12.546,10.338c3.253,4.414,4.879,10.262,4.879,17.543v62.494c0,5.428,0.31,10.611,0.931,15.567 c0.615,4.959,1.701,8.676,3.251,11.153H347.66c-0.621-1.86-1.126-3.755-1.511-5.693c-0.39-1.933-0.661-3.908-0.813-5.923 c-5.267,5.422-11.465,9.217-18.585,11.386c-7.127,2.163-14.407,3.251-21.842,3.251c-5.733,0-11.077-0.698-16.033-2.09 c-4.958-1.395-9.293-3.562-13.01-6.51c-3.718-2.938-6.622-6.656-8.713-11.147s-3.138-9.84-3.138-16.033 c0-6.813,1.199-12.43,3.604-16.84c2.399-4.417,5.495-7.939,9.295-10.575c3.793-2.632,8.129-4.607,13.01-5.923 c4.878-1.315,9.795-2.358,14.752-3.137c4.957-0.772,9.835-1.393,14.638-1.857c4.801-0.466,9.062-1.164,12.779-2.093 c3.718-0.929,6.658-2.282,8.829-4.065c2.165-1.781,3.172-4.375,3.02-7.785c0-3.56-0.58-6.389-1.742-8.479 c-1.161-2.09-2.711-3.719-4.646-4.88c-1.937-1.161-4.183-1.936-6.737-2.325c-2.557-0.382-5.309-0.58-8.248-0.58 c-6.506,0-11.617,1.395-15.335,4.183c-3.716,2.788-5.889,7.437-6.506,13.94h-32.991 C268.199,140.794,270.132,134.363,273.544,129.255z M338.713,175.838c-2.09,0.696-4.337,1.275-6.736,1.741 c-2.402,0.465-4.918,0.853-7.551,1.161c-2.635,0.313-5.268,0.698-7.899,1.163c-2.48,0.461-4.919,1.086-7.317,1.857 c-2.404,0.779-4.495,1.822-6.274,3.138c-1.784,1.317-3.216,2.985-4.3,4.994c-1.085,2.014-1.626,4.571-1.626,7.668 c0,2.94,0.541,5.422,1.626,7.431c1.084,2.017,2.558,3.604,4.416,4.765s4.025,1.976,6.507,2.438c2.475,0.466,5.031,0.698,7.665,0.698 c6.505,0,11.537-1.082,15.103-3.253c3.561-2.166,6.192-4.762,7.899-7.785c1.702-3.019,2.749-6.072,3.137-9.174 c0.384-3.097,0.58-5.576,0.58-7.434v-12.316C342.547,174.173,340.805,175.14,338.713,175.838z"/>
|
||||||
|
<path d="M463.825,111.595v22.072h-24.161v59.479c0,5.573,0.928,9.292,2.788,11.149c1.856,1.859,5.576,2.788,11.152,2.788 c1.859,0,3.638-0.076,5.343-0.232c1.703-0.152,3.33-0.388,4.878-0.696v25.557c-2.788,0.465-5.887,0.773-9.293,0.931 c-3.407,0.149-6.737,0.23-9.99,0.23c-5.111,0-9.953-0.35-14.521-1.048c-4.571-0.695-8.597-2.047-12.081-4.063 c-3.486-2.011-6.236-4.88-8.248-8.597c-2.016-3.714-3.021-8.595-3.021-14.639v-70.859h-19.98v-22.072h19.98V75.583h32.992v36.012 H463.825z"/>
|
||||||
|
<path d="M510.988,111.595V133.9h0.465c1.546-3.72,3.636-7.163,6.272-10.341c2.634-3.172,5.652-5.885,9.06-8.131 c3.405-2.242,7.047-3.985,10.923-5.228c3.868-1.237,7.898-1.859,12.081-1.859c2.168,0,4.566,0.39,7.202,1.163v30.67 c-1.551-0.312-3.41-0.584-5.576-0.814c-2.17-0.233-4.26-0.35-6.274-0.35c-6.041,0-11.152,1.01-15.332,3.021 c-4.182,2.014-7.55,4.761-10.107,8.247c-2.555,3.487-4.379,7.55-5.462,12.198c-1.083,4.645-1.625,9.682-1.625,15.102v54.133h-32.991 V111.595H510.988z"/>
|
||||||
|
<path d="M570.93,93.007V65.824h32.994v27.183H570.93z M603.924,111.595v120.117H570.93V111.595H603.924z"/>
|
||||||
|
<path d="M621.115,111.595h37.637l21.144,31.365l20.911-31.365h36.476l-39.496,56.226l44.377,63.892h-37.64l-25.093-37.87 l-25.094,37.87h-36.938l43.213-63.193L621.115,111.595z"/>
|
||||||
|
<path d="M782.443,331.097V9.711h-23.13v-7.71h32.008v336.807h-32.008v-7.711H782.443z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,219 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<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"
|
||||||
|
width="1280"
|
||||||
|
height="960"
|
||||||
|
viewBox="0 0 338.66666 254.00001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4636"
|
||||||
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
|
sodipodi:docname="roompanel-dark.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4630" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="574.88953"
|
||||||
|
inkscape:cy="546.35799"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1050"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="30"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata4633">
|
||||||
|
<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>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-42.999983)">
|
||||||
|
<circle
|
||||||
|
id="path4638"
|
||||||
|
cx="274.10834"
|
||||||
|
cy="259.42917"
|
||||||
|
r="75.40625"
|
||||||
|
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.26458335" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6"
|
||||||
|
cx="303.0802"
|
||||||
|
cy="169.86771"
|
||||||
|
r="48.286457"
|
||||||
|
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.16942617" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-7"
|
||||||
|
cx="165.76144"
|
||||||
|
cy="82.290604"
|
||||||
|
r="25.135412"
|
||||||
|
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.08819444" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-5"
|
||||||
|
cx="38.100006"
|
||||||
|
cy="195.3998"
|
||||||
|
r="76.332291"
|
||||||
|
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.26783261" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-3"
|
||||||
|
cx="87.709373"
|
||||||
|
cy="148.70103"
|
||||||
|
r="41.01041"
|
||||||
|
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.14389619" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-56"
|
||||||
|
cx="220.3979"
|
||||||
|
cy="100.94374"
|
||||||
|
r="48.286453"
|
||||||
|
style="opacity:0.25;fill:#13100e;fill-opacity:1;stroke-width:0.16942617" />
|
||||||
|
<g
|
||||||
|
id="g5310"
|
||||||
|
transform="matrix(0.18980272,0,0,0.18980272,163.39608,213.89968)"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero">
|
||||||
|
<path
|
||||||
|
id="path5231"
|
||||||
|
d="M 34.004,340.809 H 2 c -1.104,0 -2,-0.896 -2,-2 V 2 C 0,0.896 0.896,0 2,0 h 32.004 c 1.104,0 2,0.896 2,2 v 7.71 c 0,1.104 -0.896,2 -2,2 h -21.13 v 317.386 h 21.13 c 1.104,0 2,0.896 2,2.001 v 7.712 c 0,1.104 -0.896,2 -2,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5233"
|
||||||
|
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5235"
|
||||||
|
d="m 252.402,233.711 h -32.993 c -1.104,0 -2,-0.896 -2,-2 v -68.073 c 0,-3.949 -0.154,-7.722 -0.457,-11.213 -0.289,-3.282 -1.074,-6.153 -2.332,-8.53 -1.204,-2.276 -3.017,-4.119 -5.384,-5.476 -2.393,-1.362 -5.775,-2.056 -10.042,-2.056 -4.238,0 -7.674,0.798 -10.213,2.371 -2.565,1.596 -4.604,3.701 -6.053,6.258 -1.498,2.643 -2.51,5.694 -3.013,9.067 -0.526,3.513 -0.793,7.125 -0.793,10.741 v 66.91 c 0,1.104 -0.896,2 -2,2 h -32.991 c -1.104,0 -2,-0.896 -2,-2 v -67.373 c 0,-3.435 -0.078,-6.964 -0.228,-10.485 -0.148,-3.251 -0.767,-6.278 -1.841,-8.995 -1.018,-2.571 -2.667,-4.584 -5.047,-6.153 -2.372,-1.552 -6.029,-2.341 -10.865,-2.341 -1.372,0 -3.265,0.328 -5.629,0.976 -2.28,0.624 -4.536,1.826 -6.705,3.577 -2.152,1.732 -4.036,4.306 -5.605,7.655 -1.569,3.356 -2.367,7.877 -2.367,13.438 v 69.701 c 0,1.104 -0.895,2 -2,2 H 68.857 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.13 c 1.104,0 2,0.896 2,1.999 v 11.007 c 3.834,-4.499 8.248,-8.152 13.173,-10.896 6.396,-3.559 13.799,-5.362 22.002,-5.362 7.846,0 15.127,1.548 21.642,4.604 5.794,2.722 10.424,7.26 13.791,13.52 3.449,-4.362 7.833,-8.306 13.071,-11.752 6.422,-4.228 14.102,-6.371 22.824,-6.371 6.499,0 12.625,0.807 18.209,2.399 5.686,1.628 10.635,4.271 14.712,7.857 4.088,3.605 7.318,8.357 9.601,14.123 2.25,5.719 3.391,12.649 3.391,20.604 v 80.384 c -0.001,1.104 -0.896,2 -2.001,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5237"
|
||||||
|
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5239"
|
||||||
|
d="m 304.909,236.733 c -5.883,0 -11.46,-0.729 -16.574,-2.163 -5.192,-1.464 -9.806,-3.774 -13.713,-6.871 -3.944,-3.117 -7.068,-7.111 -9.282,-11.871 -2.205,-4.733 -3.324,-10.412 -3.324,-16.876 0,-7.13 1.293,-13.117 3.846,-17.797 2.542,-4.674 5.877,-8.464 9.912,-11.263 3.97,-2.752 8.556,-4.842 13.63,-6.209 4.901,-1.322 9.937,-2.394 14.961,-3.184 4.986,-0.775 9.949,-1.404 14.754,-1.872 4.679,-0.452 8.88,-1.139 12.489,-2.039 3.412,-0.854 6.118,-2.09 8.042,-3.672 1.666,-1.37 2.416,-3.384 2.292,-6.151 -0.002,-3.289 -0.502,-5.816 -1.492,-7.595 -0.998,-1.798 -2.283,-3.15 -3.927,-4.138 -1.703,-1.02 -3.725,-1.713 -6.012,-2.062 -2.47,-0.37 -5.146,-0.557 -7.947,-0.557 -6.034,0 -10.789,1.271 -14.135,3.783 -3.233,2.424 -5.155,6.64 -5.714,12.527 -0.098,1.026 -0.961,1.812 -1.992,1.812 h -32.992 c -0.552,0 -1.079,-0.229 -1.457,-0.629 -0.376,-0.402 -0.572,-0.941 -0.54,-1.491 0.485,-8.073 2.55,-14.894 6.142,-20.272 3.548,-5.331 8.147,-9.682 13.661,-12.931 5.424,-3.191 11.612,-5.498 18.392,-6.857 6.684,-1.335 13.5,-2.013 20.26,-2.013 6.096,0 12.365,0.437 18.626,1.296 6.377,0.88 12.285,2.622 17.562,5.177 5.376,2.604 9.845,6.29 13.282,10.951 3.498,4.744 5.271,11.048 5.271,18.731 v 62.494 c 0,5.307 0.306,10.462 0.915,15.319 0.576,4.64 1.572,8.116 2.963,10.338 0.385,0.616 0.407,1.395 0.055,2.031 -0.353,0.635 -1.022,1.03 -1.75,1.03 h -33.457 c -0.861,0 -1.624,-0.55 -1.898,-1.367 -0.646,-1.941 -1.176,-3.939 -1.572,-5.936 -0.141,-0.696 -0.267,-1.402 -0.38,-2.12 -4.825,4.184 -10.349,7.24 -16.474,9.105 -7.299,2.218 -14.843,3.342 -22.423,3.342 z m 37.032,-60.072 c -0.809,0.409 -1.676,0.768 -2.596,1.074 -2.161,0.72 -4.511,1.326 -6.988,1.807 -2.442,0.475 -5.033,0.872 -7.699,1.186 -2.631,0.311 -5.251,0.697 -7.784,1.146 -2.329,0.433 -4.705,1.035 -7.051,1.792 -2.194,0.711 -4.114,1.667 -5.699,2.842 -1.531,1.128 -2.785,2.587 -3.731,4.335 -0.917,1.709 -1.385,3.97 -1.385,6.719 0,2.598 0.465,4.778 1.385,6.481 0.928,1.722 2.142,3.035 3.716,4.018 1.644,1.026 3.601,1.757 5.816,2.17 2.344,0.439 4.799,0.663 7.297,0.663 6.105,0 10.836,-0.996 14.063,-2.961 3.244,-1.973 5.666,-4.349 7.199,-7.062 1.568,-2.78 2.542,-5.62 2.892,-8.436 0.376,-3.019 0.565,-5.436 0.565,-7.187 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5241"
|
||||||
|
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5243"
|
||||||
|
d="m 444.542,234.874 c -5.187,0 -10.173,-0.361 -14.823,-1.069 -4.802,-0.732 -9.104,-2.183 -12.779,-4.313 -3.789,-2.185 -6.821,-5.341 -9.006,-9.375 -2.163,-3.986 -3.26,-9.232 -3.26,-15.59 v -68.859 h -17.981 c -1.104,0 -2,-0.896 -2,-1.999 v -22.073 c 0,-1.104 0.896,-1.999 2,-1.999 h 17.981 V 75.582 c 0,-1.104 0.896,-2 2,-2 h 32.992 c 1.104,0 2,0.896 2,2 v 34.014 h 22.162 c 1.104,0 2,0.896 2,1.999 v 22.073 c 0,1.104 -0.896,1.999 -2,1.999 h -22.162 v 57.479 c 0,6.229 1.198,8.731 2.202,9.733 1.004,1.007 3.506,2.205 9.738,2.205 1.804,0 3.542,-0.076 5.161,-0.225 1.604,-0.144 3.174,-0.367 4.669,-0.665 0.13,-0.026 0.261,-0.039 0.391,-0.039 0.458,0 0.907,0.159 1.27,0.454 0.463,0.379 0.73,0.946 0.73,1.546 v 25.555 c 0,0.979 -0.707,1.813 -1.672,1.974 -2.834,0.472 -6.041,0.794 -9.527,0.957 -3.613,0.157 -6.91,0.233 -10.086,0.233 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5245"
|
||||||
|
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5247"
|
||||||
|
d="m 512.613,233.711 h -32.991 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.366 c 1.104,0 2,0.896 2,1.999 v 15.069 c 0.967,-1.516 2.034,-2.978 3.199,-4.382 2.754,-3.312 5.949,-6.182 9.496,-8.522 3.545,-2.332 7.385,-4.169 11.415,-5.462 4.056,-1.298 8.327,-1.954 12.691,-1.954 2.341,0 4.953,0.418 7.766,1.243 0.852,0.25 1.437,1.032 1.437,1.92 v 30.67 c 0,0.6 -0.269,1.167 -0.732,1.547 -0.361,0.296 -0.808,0.452 -1.265,0.452 -0.133,0 -0.265,-0.013 -0.398,-0.039 -1.484,-0.3 -3.299,-0.565 -5.392,-0.787 -2.098,-0.224 -4.136,-0.339 -6.062,-0.339 -5.706,0 -10.572,0.95 -14.467,2.823 -3.862,1.86 -7.012,4.428 -9.361,7.629 -2.389,3.263 -4.115,7.12 -5.127,11.47 -1.043,4.479 -1.574,9.409 -1.574,14.647 v 54.132 c -10e-4,1.104 -0.897,2 -2.001,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5249"
|
||||||
|
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5251"
|
||||||
|
d="M 603.923,233.711 H 570.93 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 32.994 c 1.104,0 2,0.896 2,1.999 v 120.117 c -10e-4,1.104 -0.897,2 -2.001,2 z m 0,-138.705 H 570.93 c -1.104,0 -2,-0.896 -2,-1.999 V 65.825 c 0,-1.104 0.896,-2 2,-2 h 32.994 c 1.104,0 2,0.896 2,2 v 27.182 c -10e-4,1.103 -0.897,1.999 -2.001,1.999 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5253"
|
||||||
|
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5255"
|
||||||
|
d="m 742.163,233.711 h -37.64 c -0.671,0 -1.297,-0.335 -1.667,-0.896 l -23.426,-35.352 -23.426,35.352 c -0.369,0.561 -0.995,0.896 -1.667,0.896 h -36.938 c -0.741,0 -1.424,-0.411 -1.77,-1.067 -0.345,-0.654 -0.3,-1.449 0.118,-2.061 l 42.435,-62.055 -38.71,-55.793 c -0.424,-0.613 -0.474,-1.408 -0.128,-2.069 0.343,-0.658 1.028,-1.071 1.771,-1.071 h 37.636 c 0.665,0 1.287,0.33 1.658,0.882 l 19.477,28.893 19.255,-28.884 c 0.372,-0.556 0.996,-0.891 1.665,-0.891 h 36.475 c 0.746,0 1.43,0.415 1.776,1.078 0.343,0.66 0.289,1.46 -0.139,2.071 l -38.69,55.082 43.578,62.744 c 0.424,0.61 0.474,1.408 0.128,2.066 -0.343,0.662 -1.026,1.075 -1.771,1.075 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5257"
|
||||||
|
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5259"
|
||||||
|
d="m 791.322,340.809 h -32.008 c -1.105,0 -2,-0.896 -2,-2 v -7.712 c 0,-1.105 0.896,-2.001 2,-2.001 h 21.13 V 11.71 h -21.13 c -1.105,0 -2,-0.896 -2,-2 V 2 c 0,-1.104 0.896,-2 2,-2 h 32.008 c 1.104,0 2,0.896 2,2 v 336.809 c 0,1.104 -0.896,2 -2,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5261"
|
||||||
|
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5263"
|
||||||
|
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5265"
|
||||||
|
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5267"
|
||||||
|
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5269"
|
||||||
|
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5271"
|
||||||
|
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5273"
|
||||||
|
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5275"
|
||||||
|
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5277"
|
||||||
|
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#13100e;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,219 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<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"
|
||||||
|
width="1280"
|
||||||
|
height="960"
|
||||||
|
viewBox="0 0 338.66666 254.00001"
|
||||||
|
version="1.1"
|
||||||
|
id="svg4636"
|
||||||
|
inkscape:version="0.92.2 2405546, 2018-03-11"
|
||||||
|
sodipodi:docname="drawing.svg">
|
||||||
|
<defs
|
||||||
|
id="defs4630" />
|
||||||
|
<sodipodi:namedview
|
||||||
|
id="base"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#666666"
|
||||||
|
borderopacity="1.0"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pageshadow="2"
|
||||||
|
inkscape:zoom="1"
|
||||||
|
inkscape:cx="686.38953"
|
||||||
|
inkscape:cy="426.35799"
|
||||||
|
inkscape:document-units="px"
|
||||||
|
inkscape:current-layer="layer1"
|
||||||
|
showgrid="false"
|
||||||
|
units="px"
|
||||||
|
inkscape:window-width="1920"
|
||||||
|
inkscape:window-height="1050"
|
||||||
|
inkscape:window-x="0"
|
||||||
|
inkscape:window-y="30"
|
||||||
|
inkscape:window-maximized="1" />
|
||||||
|
<metadata
|
||||||
|
id="metadata4633">
|
||||||
|
<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>
|
||||||
|
<g
|
||||||
|
inkscape:label="Layer 1"
|
||||||
|
inkscape:groupmode="layer"
|
||||||
|
id="layer1"
|
||||||
|
transform="translate(0,-42.999983)">
|
||||||
|
<circle
|
||||||
|
id="path4638"
|
||||||
|
cx="274.10834"
|
||||||
|
cy="259.42917"
|
||||||
|
r="75.40625"
|
||||||
|
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.26458335" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6"
|
||||||
|
cx="303.0802"
|
||||||
|
cy="169.86771"
|
||||||
|
r="48.286457"
|
||||||
|
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.16942617" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-7"
|
||||||
|
cx="165.76144"
|
||||||
|
cy="82.290604"
|
||||||
|
r="25.135412"
|
||||||
|
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.08819444" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-5"
|
||||||
|
cx="38.100006"
|
||||||
|
cy="195.3998"
|
||||||
|
r="76.332291"
|
||||||
|
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.26783261" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-3"
|
||||||
|
cx="87.709373"
|
||||||
|
cy="148.70103"
|
||||||
|
r="41.01041"
|
||||||
|
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.14389619" />
|
||||||
|
<circle
|
||||||
|
id="path4638-6-56"
|
||||||
|
cx="220.3979"
|
||||||
|
cy="100.94374"
|
||||||
|
r="48.286453"
|
||||||
|
style="opacity:0.25;fill:#eceff1;fill-opacity:1;stroke-width:0.16942617" />
|
||||||
|
<g
|
||||||
|
id="g5310"
|
||||||
|
transform="matrix(0.18980272,0,0,0.18980272,163.39608,213.89968)"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero">
|
||||||
|
<path
|
||||||
|
id="path5231"
|
||||||
|
d="M 34.004,340.809 H 2 c -1.104,0 -2,-0.896 -2,-2 V 2 C 0,0.896 0.896,0 2,0 h 32.004 c 1.104,0 2,0.896 2,2 v 7.71 c 0,1.104 -0.896,2 -2,2 h -21.13 v 317.386 h 21.13 c 1.104,0 2,0.896 2,2.001 v 7.712 c 0,1.104 -0.896,2 -2,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5233"
|
||||||
|
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5235"
|
||||||
|
d="m 252.402,233.711 h -32.993 c -1.104,0 -2,-0.896 -2,-2 v -68.073 c 0,-3.949 -0.154,-7.722 -0.457,-11.213 -0.289,-3.282 -1.074,-6.153 -2.332,-8.53 -1.204,-2.276 -3.017,-4.119 -5.384,-5.476 -2.393,-1.362 -5.775,-2.056 -10.042,-2.056 -4.238,0 -7.674,0.798 -10.213,2.371 -2.565,1.596 -4.604,3.701 -6.053,6.258 -1.498,2.643 -2.51,5.694 -3.013,9.067 -0.526,3.513 -0.793,7.125 -0.793,10.741 v 66.91 c 0,1.104 -0.896,2 -2,2 h -32.991 c -1.104,0 -2,-0.896 -2,-2 v -67.373 c 0,-3.435 -0.078,-6.964 -0.228,-10.485 -0.148,-3.251 -0.767,-6.278 -1.841,-8.995 -1.018,-2.571 -2.667,-4.584 -5.047,-6.153 -2.372,-1.552 -6.029,-2.341 -10.865,-2.341 -1.372,0 -3.265,0.328 -5.629,0.976 -2.28,0.624 -4.536,1.826 -6.705,3.577 -2.152,1.732 -4.036,4.306 -5.605,7.655 -1.569,3.356 -2.367,7.877 -2.367,13.438 v 69.701 c 0,1.104 -0.895,2 -2,2 H 68.857 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.13 c 1.104,0 2,0.896 2,1.999 v 11.007 c 3.834,-4.499 8.248,-8.152 13.173,-10.896 6.396,-3.559 13.799,-5.362 22.002,-5.362 7.846,0 15.127,1.548 21.642,4.604 5.794,2.722 10.424,7.26 13.791,13.52 3.449,-4.362 7.833,-8.306 13.071,-11.752 6.422,-4.228 14.102,-6.371 22.824,-6.371 6.499,0 12.625,0.807 18.209,2.399 5.686,1.628 10.635,4.271 14.712,7.857 4.088,3.605 7.318,8.357 9.601,14.123 2.25,5.719 3.391,12.649 3.391,20.604 v 80.384 c -0.001,1.104 -0.896,2 -2.001,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5237"
|
||||||
|
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5239"
|
||||||
|
d="m 304.909,236.733 c -5.883,0 -11.46,-0.729 -16.574,-2.163 -5.192,-1.464 -9.806,-3.774 -13.713,-6.871 -3.944,-3.117 -7.068,-7.111 -9.282,-11.871 -2.205,-4.733 -3.324,-10.412 -3.324,-16.876 0,-7.13 1.293,-13.117 3.846,-17.797 2.542,-4.674 5.877,-8.464 9.912,-11.263 3.97,-2.752 8.556,-4.842 13.63,-6.209 4.901,-1.322 9.937,-2.394 14.961,-3.184 4.986,-0.775 9.949,-1.404 14.754,-1.872 4.679,-0.452 8.88,-1.139 12.489,-2.039 3.412,-0.854 6.118,-2.09 8.042,-3.672 1.666,-1.37 2.416,-3.384 2.292,-6.151 -0.002,-3.289 -0.502,-5.816 -1.492,-7.595 -0.998,-1.798 -2.283,-3.15 -3.927,-4.138 -1.703,-1.02 -3.725,-1.713 -6.012,-2.062 -2.47,-0.37 -5.146,-0.557 -7.947,-0.557 -6.034,0 -10.789,1.271 -14.135,3.783 -3.233,2.424 -5.155,6.64 -5.714,12.527 -0.098,1.026 -0.961,1.812 -1.992,1.812 h -32.992 c -0.552,0 -1.079,-0.229 -1.457,-0.629 -0.376,-0.402 -0.572,-0.941 -0.54,-1.491 0.485,-8.073 2.55,-14.894 6.142,-20.272 3.548,-5.331 8.147,-9.682 13.661,-12.931 5.424,-3.191 11.612,-5.498 18.392,-6.857 6.684,-1.335 13.5,-2.013 20.26,-2.013 6.096,0 12.365,0.437 18.626,1.296 6.377,0.88 12.285,2.622 17.562,5.177 5.376,2.604 9.845,6.29 13.282,10.951 3.498,4.744 5.271,11.048 5.271,18.731 v 62.494 c 0,5.307 0.306,10.462 0.915,15.319 0.576,4.64 1.572,8.116 2.963,10.338 0.385,0.616 0.407,1.395 0.055,2.031 -0.353,0.635 -1.022,1.03 -1.75,1.03 h -33.457 c -0.861,0 -1.624,-0.55 -1.898,-1.367 -0.646,-1.941 -1.176,-3.939 -1.572,-5.936 -0.141,-0.696 -0.267,-1.402 -0.38,-2.12 -4.825,4.184 -10.349,7.24 -16.474,9.105 -7.299,2.218 -14.843,3.342 -22.423,3.342 z m 37.032,-60.072 c -0.809,0.409 -1.676,0.768 -2.596,1.074 -2.161,0.72 -4.511,1.326 -6.988,1.807 -2.442,0.475 -5.033,0.872 -7.699,1.186 -2.631,0.311 -5.251,0.697 -7.784,1.146 -2.329,0.433 -4.705,1.035 -7.051,1.792 -2.194,0.711 -4.114,1.667 -5.699,2.842 -1.531,1.128 -2.785,2.587 -3.731,4.335 -0.917,1.709 -1.385,3.97 -1.385,6.719 0,2.598 0.465,4.778 1.385,6.481 0.928,1.722 2.142,3.035 3.716,4.018 1.644,1.026 3.601,1.757 5.816,2.17 2.344,0.439 4.799,0.663 7.297,0.663 6.105,0 10.836,-0.996 14.063,-2.961 3.244,-1.973 5.666,-4.349 7.199,-7.062 1.568,-2.78 2.542,-5.62 2.892,-8.436 0.376,-3.019 0.565,-5.436 0.565,-7.187 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5241"
|
||||||
|
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5243"
|
||||||
|
d="m 444.542,234.874 c -5.187,0 -10.173,-0.361 -14.823,-1.069 -4.802,-0.732 -9.104,-2.183 -12.779,-4.313 -3.789,-2.185 -6.821,-5.341 -9.006,-9.375 -2.163,-3.986 -3.26,-9.232 -3.26,-15.59 v -68.859 h -17.981 c -1.104,0 -2,-0.896 -2,-1.999 v -22.073 c 0,-1.104 0.896,-1.999 2,-1.999 h 17.981 V 75.582 c 0,-1.104 0.896,-2 2,-2 h 32.992 c 1.104,0 2,0.896 2,2 v 34.014 h 22.162 c 1.104,0 2,0.896 2,1.999 v 22.073 c 0,1.104 -0.896,1.999 -2,1.999 h -22.162 v 57.479 c 0,6.229 1.198,8.731 2.202,9.733 1.004,1.007 3.506,2.205 9.738,2.205 1.804,0 3.542,-0.076 5.161,-0.225 1.604,-0.144 3.174,-0.367 4.669,-0.665 0.13,-0.026 0.261,-0.039 0.391,-0.039 0.458,0 0.907,0.159 1.27,0.454 0.463,0.379 0.73,0.946 0.73,1.546 v 25.555 c 0,0.979 -0.707,1.813 -1.672,1.974 -2.834,0.472 -6.041,0.794 -9.527,0.957 -3.613,0.157 -6.91,0.233 -10.086,0.233 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5245"
|
||||||
|
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5247"
|
||||||
|
d="m 512.613,233.711 h -32.991 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 31.366 c 1.104,0 2,0.896 2,1.999 v 15.069 c 0.967,-1.516 2.034,-2.978 3.199,-4.382 2.754,-3.312 5.949,-6.182 9.496,-8.522 3.545,-2.332 7.385,-4.169 11.415,-5.462 4.056,-1.298 8.327,-1.954 12.691,-1.954 2.341,0 4.953,0.418 7.766,1.243 0.852,0.25 1.437,1.032 1.437,1.92 v 30.67 c 0,0.6 -0.269,1.167 -0.732,1.547 -0.361,0.296 -0.808,0.452 -1.265,0.452 -0.133,0 -0.265,-0.013 -0.398,-0.039 -1.484,-0.3 -3.299,-0.565 -5.392,-0.787 -2.098,-0.224 -4.136,-0.339 -6.062,-0.339 -5.706,0 -10.572,0.95 -14.467,2.823 -3.862,1.86 -7.012,4.428 -9.361,7.629 -2.389,3.263 -4.115,7.12 -5.127,11.47 -1.043,4.479 -1.574,9.409 -1.574,14.647 v 54.132 c -10e-4,1.104 -0.897,2 -2.001,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5249"
|
||||||
|
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5251"
|
||||||
|
d="M 603.923,233.711 H 570.93 c -1.104,0 -2,-0.896 -2,-2 V 111.594 c 0,-1.104 0.896,-1.999 2,-1.999 h 32.994 c 1.104,0 2,0.896 2,1.999 v 120.117 c -10e-4,1.104 -0.897,2 -2.001,2 z m 0,-138.705 H 570.93 c -1.104,0 -2,-0.896 -2,-1.999 V 65.825 c 0,-1.104 0.896,-2 2,-2 h 32.994 c 1.104,0 2,0.896 2,2 v 27.182 c -10e-4,1.103 -0.897,1.999 -2.001,1.999 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5253"
|
||||||
|
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5255"
|
||||||
|
d="m 742.163,233.711 h -37.64 c -0.671,0 -1.297,-0.335 -1.667,-0.896 l -23.426,-35.352 -23.426,35.352 c -0.369,0.561 -0.995,0.896 -1.667,0.896 h -36.938 c -0.741,0 -1.424,-0.411 -1.77,-1.067 -0.345,-0.654 -0.3,-1.449 0.118,-2.061 l 42.435,-62.055 -38.71,-55.793 c -0.424,-0.613 -0.474,-1.408 -0.128,-2.069 0.343,-0.658 1.028,-1.071 1.771,-1.071 h 37.636 c 0.665,0 1.287,0.33 1.658,0.882 l 19.477,28.893 19.255,-28.884 c 0.372,-0.556 0.996,-0.891 1.665,-0.891 h 36.475 c 0.746,0 1.43,0.415 1.776,1.078 0.343,0.66 0.289,1.46 -0.139,2.071 l -38.69,55.082 43.578,62.744 c 0.424,0.61 0.474,1.408 0.128,2.066 -0.343,0.662 -1.026,1.075 -1.771,1.075 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5257"
|
||||||
|
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5259"
|
||||||
|
d="m 791.322,340.809 h -32.008 c -1.105,0 -2,-0.896 -2,-2 v -7.712 c 0,-1.105 0.896,-2.001 2,-2.001 h 21.13 V 11.71 h -21.13 c -1.105,0 -2,-0.896 -2,-2 V 2 c 0,-1.104 0.896,-2 2,-2 h 32.008 c 1.104,0 2,0.896 2,2 v 336.809 c 0,1.104 -0.896,2 -2,2 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5261"
|
||||||
|
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="opacity:0.5;fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5263"
|
||||||
|
d="m 10.875,9.711 v 321.386 h 23.13 v 7.711 H 1.999 V 2.001 h 32.006 v 7.71 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5265"
|
||||||
|
d="m 99.988,111.595 v 16.264 h 0.463 c 4.338,-6.191 9.563,-10.998 15.684,-14.406 6.117,-3.402 13.129,-5.11 21.027,-5.11 7.588,0 14.521,1.475 20.793,4.415 6.274,2.945 11.038,8.131 14.291,15.567 3.56,-5.265 8.4,-9.913 14.521,-13.94 6.117,-4.025 13.358,-6.042 21.724,-6.042 6.351,0 12.234,0.776 17.66,2.325 5.418,1.549 10.065,4.027 13.938,7.434 3.869,3.41 6.889,7.863 9.062,13.357 2.167,5.504 3.253,12.122 3.253,19.869 v 80.385 H 219.41 v -68.074 c 0,-4.025 -0.154,-7.82 -0.465,-11.385 -0.313,-3.56 -1.161,-6.656 -2.555,-9.293 -1.395,-2.631 -3.45,-4.724 -6.157,-6.274 -2.711,-1.543 -6.391,-2.322 -11.037,-2.322 -4.646,0 -8.403,0.896 -11.269,2.671 -2.868,1.784 -5.112,4.109 -6.737,6.971 -1.626,2.869 -2.711,6.12 -3.252,9.762 -0.545,3.638 -0.814,7.318 -0.814,11.035 v 66.91 h -32.991 v -67.375 c 0,-3.562 -0.081,-7.087 -0.23,-10.57 -0.158,-3.487 -0.814,-6.7 -1.978,-9.645 -1.162,-2.94 -3.099,-5.304 -5.809,-7.088 -2.711,-1.775 -6.699,-2.671 -11.965,-2.671 -1.551,0 -3.603,0.349 -6.156,1.048 -2.556,0.697 -5.036,2.016 -7.435,3.949 -2.404,1.938 -4.454,4.726 -6.158,8.363 -1.705,3.642 -2.556,8.402 -2.556,14.287 v 69.701 H 68.856 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5267"
|
||||||
|
d="m 273.544,129.255 c 3.405,-5.113 7.744,-9.215 13.012,-12.316 5.264,-3.097 11.186,-5.303 17.771,-6.621 6.582,-1.315 13.205,-1.976 19.865,-1.976 6.042,0 12.158,0.428 18.354,1.277 6.195,0.855 11.85,2.522 16.962,4.997 5.111,2.477 9.292,5.926 12.546,10.338 3.253,4.414 4.879,10.262 4.879,17.543 v 62.494 c 0,5.428 0.31,10.611 0.931,15.567 0.615,4.959 1.701,8.676 3.251,11.153 H 347.66 c -0.621,-1.86 -1.126,-3.755 -1.511,-5.693 -0.39,-1.933 -0.661,-3.908 -0.813,-5.923 -5.267,5.422 -11.465,9.217 -18.585,11.386 -7.127,2.163 -14.407,3.251 -21.842,3.251 -5.733,0 -11.077,-0.698 -16.033,-2.09 -4.958,-1.395 -9.293,-3.562 -13.01,-6.51 -3.718,-2.938 -6.622,-6.656 -8.713,-11.147 -2.091,-4.491 -3.138,-9.84 -3.138,-16.033 0,-6.813 1.199,-12.43 3.604,-16.84 2.399,-4.417 5.495,-7.939 9.295,-10.575 3.793,-2.632 8.129,-4.607 13.01,-5.923 4.878,-1.315 9.795,-2.358 14.752,-3.137 4.957,-0.772 9.835,-1.393 14.638,-1.857 4.801,-0.466 9.062,-1.164 12.779,-2.093 3.718,-0.929 6.658,-2.282 8.829,-4.065 2.165,-1.781 3.172,-4.375 3.02,-7.785 0,-3.56 -0.58,-6.389 -1.742,-8.479 -1.161,-2.09 -2.711,-3.719 -4.646,-4.88 -1.937,-1.161 -4.183,-1.936 -6.737,-2.325 -2.557,-0.382 -5.309,-0.58 -8.248,-0.58 -6.506,0 -11.617,1.395 -15.335,4.183 -3.716,2.788 -5.889,7.437 -6.506,13.94 h -32.991 c 0.462,-7.742 2.395,-14.173 5.807,-19.281 z m 65.169,46.583 c -2.09,0.696 -4.337,1.275 -6.736,1.741 -2.402,0.465 -4.918,0.853 -7.551,1.161 -2.635,0.313 -5.268,0.698 -7.899,1.163 -2.48,0.461 -4.919,1.086 -7.317,1.857 -2.404,0.779 -4.495,1.822 -6.274,3.138 -1.784,1.317 -3.216,2.985 -4.3,4.994 -1.085,2.014 -1.626,4.571 -1.626,7.668 0,2.94 0.541,5.422 1.626,7.431 1.084,2.017 2.558,3.604 4.416,4.765 1.858,1.161 4.025,1.976 6.507,2.438 2.475,0.466 5.031,0.698 7.665,0.698 6.505,0 11.537,-1.082 15.103,-3.253 3.561,-2.166 6.192,-4.762 7.899,-7.785 1.702,-3.019 2.749,-6.072 3.137,-9.174 0.384,-3.097 0.58,-5.576 0.58,-7.434 V 172.93 c -1.396,1.243 -3.138,2.21 -5.23,2.908 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5269"
|
||||||
|
d="m 463.825,111.595 v 22.072 h -24.161 v 59.479 c 0,5.573 0.928,9.292 2.788,11.149 1.856,1.859 5.576,2.788 11.152,2.788 1.859,0 3.638,-0.076 5.343,-0.232 1.703,-0.152 3.33,-0.388 4.878,-0.696 v 25.557 c -2.788,0.465 -5.887,0.773 -9.293,0.931 -3.407,0.149 -6.737,0.23 -9.99,0.23 -5.111,0 -9.953,-0.35 -14.521,-1.048 -4.571,-0.695 -8.597,-2.047 -12.081,-4.063 -3.486,-2.011 -6.236,-4.88 -8.248,-8.597 -2.016,-3.714 -3.021,-8.595 -3.021,-14.639 v -70.859 h -19.98 v -22.072 h 19.98 V 75.583 h 32.992 v 36.012 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5271"
|
||||||
|
d="M 510.988,111.595 V 133.9 h 0.465 c 1.546,-3.72 3.636,-7.163 6.272,-10.341 2.634,-3.172 5.652,-5.885 9.06,-8.131 3.405,-2.242 7.047,-3.985 10.923,-5.228 3.868,-1.237 7.898,-1.859 12.081,-1.859 2.168,0 4.566,0.39 7.202,1.163 v 30.67 c -1.551,-0.312 -3.41,-0.584 -5.576,-0.814 -2.17,-0.233 -4.26,-0.35 -6.274,-0.35 -6.041,0 -11.152,1.01 -15.332,3.021 -4.182,2.014 -7.55,4.761 -10.107,8.247 -2.555,3.487 -4.379,7.55 -5.462,12.198 -1.083,4.645 -1.625,9.682 -1.625,15.102 v 54.133 H 479.624 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5273"
|
||||||
|
d="M 570.93,93.007 V 65.824 h 32.994 v 27.183 z m 32.994,18.588 V 231.712 H 570.93 V 111.595 Z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5275"
|
||||||
|
d="m 621.115,111.595 h 37.637 l 21.144,31.365 20.911,-31.365 h 36.476 l -39.496,56.226 44.377,63.892 h -37.64 l -25.093,-37.87 -25.094,37.87 h -36.938 l 43.213,-63.193 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
<path
|
||||||
|
id="path5277"
|
||||||
|
d="M 782.443,331.097 V 9.711 h -23.13 v -7.71 h 32.008 v 336.807 h -32.008 v -7.711 z"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#eceff1;fill-opacity:1;fill-rule:nonzero" />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 22 KiB |
|
@ -1,6 +1,6 @@
|
||||||
id: org.eu.encom.spectral
|
id: org.eu.encom.spectral
|
||||||
runtime: org.kde.Platform
|
runtime: org.kde.Platform
|
||||||
runtime-version: 5.11
|
runtime-version: 5.12
|
||||||
sdk: org.kde.Sdk
|
sdk: org.kde.Sdk
|
||||||
command: spectral
|
command: spectral
|
||||||
finish-args:
|
finish-args:
|
||||||
|
@ -15,6 +15,8 @@ finish-args:
|
||||||
modules:
|
modules:
|
||||||
- name: spectral
|
- name: spectral
|
||||||
buildsystem: qmake
|
buildsystem: qmake
|
||||||
|
config-opts:
|
||||||
|
- "BUNDLE_FONT=true"
|
||||||
sources:
|
sources:
|
||||||
- type: dir
|
- type: dir
|
||||||
path: ../
|
path: ../
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
<RCC>
|
||||||
|
<qresource prefix="/">
|
||||||
|
<file>assets/font/roboto.ttf</file>
|
||||||
|
<file>assets/font/twemoji.ttf</file>
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
|
@ -1,4 +1,4 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
ScrollHelper {
|
ScrollHelper {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
|
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
|
|
|
@ -0,0 +1,64 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
property string hint: "H"
|
||||||
|
property string source: ""
|
||||||
|
readonly property url realSource: source ? "image://mxc/" + source : ""
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
id: image
|
||||||
|
visible: realSource
|
||||||
|
source: width < 1 ? "" : realSource
|
||||||
|
sourceSize.width: width
|
||||||
|
sourceSize.height: width
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
mipmap: true
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: image.width
|
||||||
|
height: image.width
|
||||||
|
|
||||||
|
radius: width / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
visible: !realSource || image.status != Image.Ready
|
||||||
|
|
||||||
|
radius: height / 2
|
||||||
|
|
||||||
|
color: stringToColor(hint)
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
color: "white"
|
||||||
|
text: hint[0].toUpperCase()
|
||||||
|
font.pixelSize: root.width / 2
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function stringToColor(str) {
|
||||||
|
var hash = 0;
|
||||||
|
for (var i = 0; i < str.length; i++) {
|
||||||
|
hash = str.charCodeAt(i) + ((hash << 5) - hash);
|
||||||
|
}
|
||||||
|
var colour = '#';
|
||||||
|
for (var j = 0; j < 3; j++) {
|
||||||
|
var value = (hash >> (j * 8)) & 0xFF;
|
||||||
|
colour += ('00' + value.toString(16)).substr(-2);
|
||||||
|
}
|
||||||
|
return colour;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,94 +1,107 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
Popup {
|
ColumnLayout {
|
||||||
property var emojiModel
|
|
||||||
property var textArea
|
|
||||||
property string emojiCategory: "people"
|
property string emojiCategory: "people"
|
||||||
|
property var textArea
|
||||||
|
property var emojiModel
|
||||||
|
|
||||||
ColumnLayout {
|
spacing: 0
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
GridView {
|
ListView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.preferredHeight: 48
|
||||||
|
Layout.leftMargin: 24
|
||||||
|
Layout.rightMargin: 24
|
||||||
|
|
||||||
cellWidth: 36
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
cellHeight: 36
|
|
||||||
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
clip: true
|
||||||
|
|
||||||
clip: true
|
orientation: ListView.Horizontal
|
||||||
|
|
||||||
model: emojiModel.model[emojiCategory]
|
model: ListModel {
|
||||||
|
ListElement { label: "😏"; category: "people" }
|
||||||
delegate: ItemDelegate {
|
ListElement { label: "🌲"; category: "nature" }
|
||||||
width: 36
|
ListElement { label: "🍛"; category: "food"}
|
||||||
height: 36
|
ListElement { label: "🚁"; category: "activity" }
|
||||||
|
ListElement { label: "🚅"; category: "travel" }
|
||||||
contentItem: Text {
|
ListElement { label: "💡"; category: "objects" }
|
||||||
horizontalAlignment: Text.AlignHCenter
|
ListElement { label: "🔣"; category: "symbols" }
|
||||||
verticalAlignment: Text.AlignVCenter
|
ListElement { label: "🏁"; category: "flags" }
|
||||||
|
|
||||||
font.pointSize: 20
|
|
||||||
font.family: "Emoji"
|
|
||||||
text: modelData.unicode
|
|
||||||
}
|
|
||||||
|
|
||||||
hoverEnabled: true
|
|
||||||
ToolTip.text: modelData.shortname
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
|
|
||||||
onClicked: textArea.insert(textArea.cursorPosition, modelData.unicode)
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
delegate: ItemDelegate {
|
||||||
Layout.fillWidth: true
|
width: 64
|
||||||
Layout.preferredHeight: 2
|
height: 48
|
||||||
|
|
||||||
color: Material.accent
|
contentItem: Label {
|
||||||
}
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
Row {
|
font.pixelSize: 24
|
||||||
Repeater {
|
text: label
|
||||||
model: ListModel {
|
|
||||||
ListElement { label: "😏"; category: "people" }
|
|
||||||
ListElement { label: "🌲"; category: "nature" }
|
|
||||||
ListElement { label: "🍛"; category: "food"}
|
|
||||||
ListElement { label: "🚁"; category: "activity" }
|
|
||||||
ListElement { label: "🚅"; category: "travel" }
|
|
||||||
ListElement { label: "💡"; category: "objects" }
|
|
||||||
ListElement { label: "🔣"; category: "symbols" }
|
|
||||||
ListElement { label: "🏁"; category: "flags" }
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
|
||||||
width: 36
|
|
||||||
height: 36
|
|
||||||
|
|
||||||
contentItem: Text {
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
|
|
||||||
font.pointSize: 20
|
|
||||||
font.family: "Emoji"
|
|
||||||
text: label
|
|
||||||
}
|
|
||||||
|
|
||||||
hoverEnabled: true
|
|
||||||
ToolTip.text: category
|
|
||||||
ToolTip.visible: hovered
|
|
||||||
|
|
||||||
onClicked: emojiCategory = category
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: 2
|
||||||
|
|
||||||
|
visible: emojiCategory === category
|
||||||
|
|
||||||
|
color: Material.accent
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: emojiCategory = category
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
Layout.leftMargin: 12
|
||||||
|
Layout.rightMargin: 12
|
||||||
|
|
||||||
|
color: MSettings.darkTheme ? "#424242" : "#e7ebeb"
|
||||||
|
}
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 180
|
||||||
|
|
||||||
|
cellWidth: 48
|
||||||
|
cellHeight: 48
|
||||||
|
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
model: emojiModel.model[emojiCategory]
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
width: 48
|
||||||
|
height: 48
|
||||||
|
|
||||||
|
contentItem: Label {
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
||||||
|
font.pixelSize: 32
|
||||||
|
text: modelData.unicode
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: textArea.insert(textArea.cursorPosition, modelData.unicode)
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
import Spectral.Font 0.1
|
import Spectral.Font 0.1
|
||||||
|
@ -10,8 +10,8 @@ Text {
|
||||||
|
|
||||||
id: materialLabel
|
id: materialLabel
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "white" : "dark"
|
color: MPalette.foreground
|
||||||
font.pointSize: 16
|
font.pixelSize: 24
|
||||||
font.family: MaterialFont.name
|
font.family: MaterialFont.name
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: root
|
id: root
|
||||||
|
@ -66,14 +66,13 @@ MouseArea {
|
||||||
}
|
}
|
||||||
|
|
||||||
onWheel: {
|
onWheel: {
|
||||||
var newPos = calculateNewPosition(flickable, wheel);
|
|
||||||
// console.warn("Delta: ", wheel.pixelDelta.y);
|
// console.warn("Delta: ", wheel.pixelDelta.y);
|
||||||
// console.warn("Old position: ", flickable.contentY);
|
// console.warn("Old position: ", flickable.contentY);
|
||||||
// console.warn("New position: ", newPos);
|
// console.warn("New position: ", newPos);
|
||||||
|
|
||||||
// Show the scrollbars
|
// Show the scrollbars
|
||||||
flickable.flick(0, 0);
|
flickable.flick(0, 0);
|
||||||
flickable.contentY = newPos;
|
flickable.contentY = calculateNewPosition(flickable, wheel);
|
||||||
cancelFlickStateTimer.start()
|
cancelFlickStateTimer.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
import "qrc:/js/util.js" as Util
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
property var page
|
|
||||||
property bool selected: stackView.currentItem === page
|
|
||||||
property color highlightColor: Material.accent
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: selected ? 4 : 0
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
color: highlightColor
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: Util.pushToStack(stackView, page)
|
|
||||||
}
|
|
|
@ -37,10 +37,11 @@
|
||||||
**
|
**
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Window 2.1
|
import QtQuick.Window 2.1
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
|
@ -64,7 +65,7 @@ Item {
|
||||||
property Component handleDelegate: Rectangle {
|
property Component handleDelegate: Rectangle {
|
||||||
width: 1
|
width: 1
|
||||||
height: 1
|
height: 1
|
||||||
visible: false
|
color: MSettings.darkTheme ? "#424242" : "#E1E1E1"
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -88,7 +89,7 @@ Item {
|
||||||
|
|
||||||
/*! \qmlmethod void SplitView::addItem(Item item)
|
/*! \qmlmethod void SplitView::addItem(Item item)
|
||||||
Add an item to the end of the view.
|
Add an item to the end of the view.
|
||||||
\since QtQuick.Controls 1.3 */
|
\since QtQuick.Controls 1.12 */
|
||||||
function addItem(item) {
|
function addItem(item) {
|
||||||
d.updateLayoutGuard = true
|
d.updateLayoutGuard = true
|
||||||
d.addItem_impl(item)
|
d.addItem_impl(item)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
import Qt.labs.platform 1.0
|
import Qt.labs.platform 1.0
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import QtQuick.Controls.Material 2.12
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import Qt.labs.platform 1.0 as Platform
|
||||||
|
|
||||||
|
import Spectral 0.1
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Font 0.1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
||||||
|
readonly property bool sentByMe: author === currentRoom.localUser
|
||||||
|
|
||||||
|
property bool openOnFinished: false
|
||||||
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile()
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: 48
|
||||||
|
|
||||||
|
text: author.displayName
|
||||||
|
|
||||||
|
visible: avatarVisible
|
||||||
|
|
||||||
|
font.pixelSize: 13
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
|
||||||
|
z: -5
|
||||||
|
|
||||||
|
id: messageRow
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
visible: avatarVisible
|
||||||
|
hint: author.displayName
|
||||||
|
source: author.avatarMediaId
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
visible: !(sentByMe || avatarVisible)
|
||||||
|
|
||||||
|
text: Qt.formatDateTime(time, "hh:mm")
|
||||||
|
color: "#5B7480"
|
||||||
|
|
||||||
|
font.pixelSize: 10
|
||||||
|
horizontalAlignment: Label.AlignHCenter
|
||||||
|
verticalAlignment: Label.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
|
||||||
|
|
||||||
|
padding: 12
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
ToolButton {
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
icon: progressInfo.completed ? "\ue5ca" : "\ue2c4"
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: progressInfo.completed ? openSavedFile() : saveFileAs()
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Label {
|
||||||
|
Layout.alignment: Qt.AlignVCenter
|
||||||
|
|
||||||
|
text: display
|
||||||
|
font.pixelSize: 18
|
||||||
|
font.weight: Font.Medium
|
||||||
|
font.capitalization: Font.AllUppercase
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: progressInfo.active ? (progressInfo.progress + "/" + progressInfo.total) : content.info.size
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: MPalette.banner
|
||||||
|
radius: 18
|
||||||
|
|
||||||
|
AutoMouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
id: messageMouseArea
|
||||||
|
|
||||||
|
onSecondaryClicked: messageContextMenu.popup()
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: messageContextMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "View Source"
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
sourceDialog.sourceText = toolTip
|
||||||
|
sourceDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Open Externally"
|
||||||
|
|
||||||
|
onTriggered: downloadAndOpen()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Save As"
|
||||||
|
|
||||||
|
onTriggered: saveFileAs()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Reply"
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
roomPanelInput.replyUser = author
|
||||||
|
roomPanelInput.replyEventID = eventId
|
||||||
|
roomPanelInput.replyContent = message
|
||||||
|
roomPanelInput.isReply = true
|
||||||
|
roomPanelInput.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Redact"
|
||||||
|
|
||||||
|
onTriggered: currentRoom.redactEvent(eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveFileAs() { currentRoom.saveFileAs(eventId) }
|
||||||
|
|
||||||
|
function downloadAndOpen()
|
||||||
|
{
|
||||||
|
if (downloaded) openSavedFile()
|
||||||
|
else
|
||||||
|
{
|
||||||
|
openOnFinished = true
|
||||||
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_") + (message || ".tmp"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSavedFile()
|
||||||
|
{
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,37 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
import Spectral.Effect 2.0
|
|
||||||
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
Control {
|
|
||||||
property bool highlighted: false
|
|
||||||
property bool colored: false
|
|
||||||
|
|
||||||
readonly property bool darkBackground: highlighted ? true : MSettings.darkTheme
|
|
||||||
|
|
||||||
padding: 12
|
|
||||||
|
|
||||||
AutoMouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onSecondaryClicked: {
|
|
||||||
messageContextMenu.row = messageRow
|
|
||||||
messageContextMenu.model = model
|
|
||||||
messageContextMenu.selectedText = contentLabel.selectedText
|
|
||||||
messageContextMenu.popup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: colored ? Material.accent : highlighted ? Material.primary : Material.background
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: ElevationEffect {
|
|
||||||
elevation: 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,168 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import QtQuick.Controls.Material 2.12
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
import Qt.labs.platform 1.0 as Platform
|
||||||
|
|
||||||
|
import Spectral 0.1
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Font 0.1
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
||||||
|
readonly property bool sentByMe: author === currentRoom.localUser
|
||||||
|
|
||||||
|
property bool openOnFinished: false
|
||||||
|
readonly property bool downloaded: progressInfo && progressInfo.completed
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
onDownloadedChanged: if (downloaded && openOnFinished) openSavedFile()
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.leftMargin: 48
|
||||||
|
|
||||||
|
text: author.displayName
|
||||||
|
|
||||||
|
visible: avatarVisible
|
||||||
|
|
||||||
|
font.pixelSize: 13
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
|
||||||
|
z: -5
|
||||||
|
|
||||||
|
id: messageRow
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
visible: avatarVisible
|
||||||
|
hint: author.displayName
|
||||||
|
source: author.avatarMediaId
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
visible: !(sentByMe || avatarVisible)
|
||||||
|
|
||||||
|
text: Qt.formatDateTime(time, "hh:mm")
|
||||||
|
color: "#5B7480"
|
||||||
|
|
||||||
|
font.pixelSize: 10
|
||||||
|
horizontalAlignment: Label.AlignHCenter
|
||||||
|
verticalAlignment: Label.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
|
||||||
|
|
||||||
|
id: img
|
||||||
|
|
||||||
|
source: downloaded ? progressInfo.localPath : "image://mxc/" +
|
||||||
|
(content.info && content.info.thumbnail_info ?
|
||||||
|
content.thumbnailMediaId : content.mediaId)
|
||||||
|
sourceSize.width: 200
|
||||||
|
sourceSize.height: 200
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: OpacityMask {
|
||||||
|
maskSource: Rectangle {
|
||||||
|
width: img.width
|
||||||
|
height: img.height
|
||||||
|
radius: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
color: "transparent"
|
||||||
|
radius: 24
|
||||||
|
|
||||||
|
border.width: 2
|
||||||
|
border.color: MPalette.banner
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoMouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
id: messageMouseArea
|
||||||
|
|
||||||
|
onSecondaryClicked: messageContextMenu.popup()
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: messageContextMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "View Source"
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
sourceDialog.sourceText = toolTip
|
||||||
|
sourceDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Open Externally"
|
||||||
|
|
||||||
|
onTriggered: downloadAndOpen()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Save As"
|
||||||
|
|
||||||
|
onTriggered: saveFileAs()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Reply"
|
||||||
|
|
||||||
|
onTriggered: {
|
||||||
|
roomPanelInput.replyUser = author
|
||||||
|
roomPanelInput.replyEventID = eventId
|
||||||
|
roomPanelInput.replyContent = message
|
||||||
|
roomPanelInput.isReply = true
|
||||||
|
roomPanelInput.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Redact"
|
||||||
|
|
||||||
|
onTriggered: currentRoom.redactEvent(eventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveFileAs() { currentRoom.saveFileAs(eventId) }
|
||||||
|
|
||||||
|
function downloadAndOpen()
|
||||||
|
{
|
||||||
|
if (downloaded) openSavedFile()
|
||||||
|
else
|
||||||
|
{
|
||||||
|
openOnFinished = true
|
||||||
|
currentRoom.downloadFile(eventId, Platform.StandardPaths.writableLocation(Platform.StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_") + (message || ".tmp"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function openSavedFile()
|
||||||
|
{
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localPath)) return;
|
||||||
|
if (Qt.openUrlExternally(progressInfo.localDir)) return;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,254 +1,217 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Font 0.1
|
||||||
|
|
||||||
RowLayout {
|
ColumnLayout {
|
||||||
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
readonly property bool avatarVisible: !sentByMe && (aboveAuthor !== author || aboveSection !== section || aboveEventType === "state" || aboveEventType === "emote" || aboveEventType === "other")
|
||||||
readonly property bool highlighted: !(sentByMe || eventType === "notice" )
|
|
||||||
readonly property bool sentByMe: author === currentRoom.localUser
|
readonly property bool sentByMe: author === currentRoom.localUser
|
||||||
readonly property bool isText: eventType === "notice" || eventType === "message"
|
|
||||||
|
|
||||||
signal saveFileAs()
|
signal saveFileAs()
|
||||||
signal openExternally()
|
signal openExternally()
|
||||||
|
|
||||||
z: -5
|
|
||||||
|
|
||||||
id: messageRow
|
|
||||||
|
|
||||||
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
|
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
|
||||||
|
|
||||||
spacing: 6
|
id: root
|
||||||
|
|
||||||
ImageItem {
|
spacing: 0
|
||||||
Layout.preferredWidth: 40
|
|
||||||
Layout.preferredHeight: 40
|
Label {
|
||||||
Layout.alignment: Qt.AlignTop
|
Layout.leftMargin: 48
|
||||||
|
|
||||||
|
text: author.displayName
|
||||||
|
|
||||||
round: false
|
|
||||||
visible: avatarVisible
|
visible: avatarVisible
|
||||||
hint: author.displayName
|
|
||||||
source: author.paintable
|
font.pixelSize: 13
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
RowLayout {
|
||||||
Layout.preferredWidth: 40
|
z: -5
|
||||||
Layout.preferredHeight: 40
|
|
||||||
Layout.alignment: Qt.AlignTop
|
|
||||||
|
|
||||||
color: "transparent"
|
id: messageRow
|
||||||
visible: !(sentByMe || avatarVisible)
|
|
||||||
}
|
|
||||||
|
|
||||||
GenericBubble {
|
spacing: 4
|
||||||
Layout.maximumWidth: messageListView.width - (!sentByMe ? 40 + messageRow.spacing : 0)
|
|
||||||
|
|
||||||
id: genericBubble
|
Avatar {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
highlighted: messageRow.highlighted
|
visible: avatarVisible
|
||||||
colored: highlighted && (eventType === "notice" || highlight)
|
hint: author.displayName
|
||||||
|
source: author.avatarMediaId
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: ColumnLayout {
|
Label {
|
||||||
id: messageColumn
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
spacing: 0
|
visible: !(sentByMe || avatarVisible)
|
||||||
|
|
||||||
TimelineLabel {
|
text: Qt.formatDateTime(time, "hh:mm")
|
||||||
Layout.fillWidth: true
|
color: MPalette.lighter
|
||||||
|
|
||||||
id: authorLabel
|
font.pixelSize: 10
|
||||||
|
horizontalAlignment: Label.AlignHCenter
|
||||||
|
verticalAlignment: Label.AlignVCenter
|
||||||
|
}
|
||||||
|
|
||||||
visible: messageRow.avatarVisible
|
Control {
|
||||||
text: author.displayName
|
Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
|
||||||
Material.foreground: Material.accent
|
|
||||||
coloredBackground: highlighted
|
|
||||||
font.bold: true
|
|
||||||
|
|
||||||
MouseArea {
|
verticalPadding: 8
|
||||||
|
horizontalPadding: 16
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: sentByMe ? "#009DC2" : eventType === "notice" ? "#4285F4" : "#673AB7"
|
||||||
|
radius: 18
|
||||||
|
|
||||||
|
AutoMouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
|
||||||
onClicked: roomPanelInput.insert(author.displayName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TextEdit {
|
id: messageMouseArea
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: contentLabel
|
onSecondaryClicked: messageContextMenu.popup()
|
||||||
|
|
||||||
text: (highlighted ? "<style>a{color: white;} .user-pill{color: white}</style>" : "<style>a{color: " + Material.accent + ";} .user-pill{color: " + Material.accent + "}</style>") + display
|
Menu {
|
||||||
|
readonly property string selectedText: contentLabel.selectedText
|
||||||
|
|
||||||
visible: isText
|
id: messageContextMenu
|
||||||
color: highlighted ? "white": Material.foreground
|
|
||||||
|
|
||||||
font.family: authorLabel.font.family
|
MenuItem {
|
||||||
font.pointSize: 10
|
text: "View Source"
|
||||||
selectByMouse: true
|
|
||||||
readOnly: true
|
|
||||||
wrapMode: Label.Wrap
|
|
||||||
selectedTextColor: highlighted ? Material.accent : "white"
|
|
||||||
selectionColor: highlighted ? "white" : Material.accent
|
|
||||||
textFormat: Text.RichText
|
|
||||||
|
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onTriggered: {
|
||||||
|
sourceDialog.sourceText = toolTip
|
||||||
|
sourceDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Reply"
|
||||||
|
|
||||||
MouseArea {
|
onTriggered: {
|
||||||
anchors.fill: parent
|
roomPanelInput.replyUser = author
|
||||||
acceptedButtons: Qt.NoButton
|
roomPanelInput.replyEventID = eventId
|
||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
roomPanelInput.replyContent = messageContextMenu.selectedText || message
|
||||||
}
|
roomPanelInput.isReply = true
|
||||||
}
|
roomPanelInput.focus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Redact"
|
||||||
|
|
||||||
Loader {
|
onTriggered: currentRoom.redactEvent(eventId)
|
||||||
sourceComponent: {
|
}
|
||||||
switch (eventType) {
|
|
||||||
case "image":
|
|
||||||
return imageComponent
|
|
||||||
case "file":
|
|
||||||
return fileComponent
|
|
||||||
case "audio":
|
|
||||||
return audioComponent
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
active: eventType === "image" || eventType === "file" || eventType === "audio"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Row {
|
contentItem: ColumnLayout {
|
||||||
Layout.alignment: Qt.AlignRight
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
spacing: 4
|
visible: replyEventId || ""
|
||||||
|
|
||||||
TimelineLabel {
|
padding: 8
|
||||||
visible: userMarker.length > 5
|
|
||||||
text: userMarker.length - 5 + "+"
|
|
||||||
coloredBackground: highlighted
|
|
||||||
Material.foreground: "grey"
|
|
||||||
font.pointSize: 8
|
|
||||||
}
|
|
||||||
|
|
||||||
Repeater {
|
background: Item {
|
||||||
model: userMarker.length > 5 ? userMarker.slice(0, 5) : userMarker
|
Rectangle {
|
||||||
|
anchors.leftMargin: 0
|
||||||
ImageItem {
|
width: 2
|
||||||
width: parent.height
|
height: parent.height
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
hint: modelData.displayName
|
|
||||||
source: modelData.paintable
|
|
||||||
|
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
cursorShape: Qt.PointingHandCursor
|
onClicked: goToEvent(replyEventId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: {
|
contentItem: RowLayout {
|
||||||
readMarkerDialog.listModel = userMarker
|
spacing: 8
|
||||||
readMarkerDialog.open()
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 36
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
source: replyAuthor ? replyAuthor.avatarMediaId : ""
|
||||||
|
hint: replyAuthor ? replyAuthor.displayName : "H"
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: "white"
|
||||||
|
text: replyAuthor ? replyAuthor.displayName : ""
|
||||||
|
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Medium
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
color: "white"
|
||||||
|
text: replyDisplay || ""
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
textFormat: Label.RichText
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TimelineLabel {
|
TextEdit {
|
||||||
id: timeLabel
|
Layout.fillWidth: true
|
||||||
|
|
||||||
visible: Math.abs(time - aboveTime) > 600000 || index == 0
|
id: contentLabel
|
||||||
text: Qt.formatTime(time, "hh:mm")
|
|
||||||
coloredBackground: highlighted
|
|
||||||
Material.foreground: "grey"
|
|
||||||
font.pointSize: 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
text: "<style>a{color: white;} .user-pill{}</style>" + display
|
||||||
id: imageComponent
|
|
||||||
|
|
||||||
DownloadableContent {
|
color: "white"
|
||||||
width: messageImage.width
|
|
||||||
height: messageImage.height
|
|
||||||
|
|
||||||
id: downloadable
|
font.family: CommonFont.font.family
|
||||||
|
font.pixelSize: 14
|
||||||
|
selectByMouse: true
|
||||||
|
readOnly: true
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
selectedTextColor: Material.accent
|
||||||
|
selectionColor: "white"
|
||||||
|
textFormat: Text.RichText
|
||||||
|
|
||||||
TimelineImage {
|
onLinkActivated: {
|
||||||
z: -4
|
if (link.startsWith("https://matrix.to/")) {
|
||||||
|
var result = link.replace(/\?.*/, "").match("https://matrix.to/#/(!.*:.*)/(\\$.*:.*)")
|
||||||
id: messageImage
|
if (result.length < 3) return
|
||||||
|
if (result[1] != currentRoom.id) return
|
||||||
sourceSize: 128
|
if (!result[2]) return
|
||||||
source: "image://mxc/" + (content.thumbnail_url ? content.thumbnail_url : content.url)
|
goToEvent(result[2])
|
||||||
|
} else {
|
||||||
onClicked: downloadAndOpen()
|
Qt.openUrlExternally(link)
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
messageRow.saveFileAs.connect(saveFileAs)
|
|
||||||
messageRow.openExternally.connect(downloadAndOpen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: fileComponent
|
|
||||||
|
|
||||||
TimelineLabel {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: downloadDelegate
|
|
||||||
|
|
||||||
text: "<b>File: </b>" + content.body
|
|
||||||
coloredBackground: highlighted
|
|
||||||
|
|
||||||
background: DownloadableContent {
|
|
||||||
id: downloadable
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
messageRow.saveFileAs.connect(saveFileAs)
|
|
||||||
messageRow.openExternally.connect(downloadAndOpen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
|
||||||
id: audioComponent
|
|
||||||
|
|
||||||
TimelineLabel {
|
|
||||||
id: downloadDelegate
|
|
||||||
|
|
||||||
text: content.info.duration / 1000 + '"'
|
|
||||||
coloredBackground: highlighted
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
propagateComposedEvents: true
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (downloadable.downloaded)
|
|
||||||
spectralController.playAudio(progressInfo.localPath)
|
|
||||||
else
|
|
||||||
{
|
|
||||||
playOnFinished = true
|
|
||||||
currentRoom.downloadFile(eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + eventId.replace(":", "_") + ".tmp")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
background: DownloadableContent {
|
MouseArea {
|
||||||
id: downloadable
|
anchors.fill: parent
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
onDownloadedChanged: downloaded && playOnFinished ? spectralController.playAudio(progressInfo.localPath) : {}
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
messageRow.saveFileAs.connect(saveFileAs)
|
|
||||||
messageRow.openExternally.connect(downloadAndOpen)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: section + " • " + Qt.formatTime(time, "hh:mm")
|
||||||
|
color: MPalette.foreground
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Medium
|
||||||
|
font.capitalization: Font.AllUppercase
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
|
@ -1,24 +1,27 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
text: "<b>" + author.displayName + "</b> " + display
|
text: "<b>" + author.displayName + "</b> " + display
|
||||||
color: "white"
|
color: MPalette.foreground
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.weight: Font.Medium
|
||||||
|
|
||||||
padding: 8
|
topPadding: 8
|
||||||
|
bottomPadding: 8
|
||||||
|
leftPadding: 24
|
||||||
|
rightPadding: 24
|
||||||
|
|
||||||
wrapMode: Label.Wrap
|
wrapMode: Label.Wrap
|
||||||
linkColor: "white"
|
|
||||||
textFormat: MSettings.richText ? Text.RichText : Text.StyledText
|
textFormat: MSettings.richText ? Text.RichText : Text.StyledText
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
onLinkActivated: Qt.openUrlExternally(link)
|
||||||
|
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
color: MSettings.darkTheme ? "#484848" : "grey"
|
color: MPalette.banner
|
||||||
|
radius: 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
|
|
||||||
Item {
|
|
||||||
property alias source: baseImage.source
|
|
||||||
property alias sourceSize: baseImage.sourceSize.width
|
|
||||||
|
|
||||||
readonly property bool loading: baseImage.status == Image.Loading
|
|
||||||
|
|
||||||
signal clicked()
|
|
||||||
|
|
||||||
width: loading ? 128 : baseImage.implicitWidth
|
|
||||||
height: loading ? progressBar.height : baseImage.implicitHeight
|
|
||||||
|
|
||||||
id: rekt
|
|
||||||
|
|
||||||
Image { id: baseImage }
|
|
||||||
|
|
||||||
ProgressBar {
|
|
||||||
width: parent.width
|
|
||||||
visible: loading
|
|
||||||
|
|
||||||
id: progressBar
|
|
||||||
|
|
||||||
indeterminate: true
|
|
||||||
}
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
propagateComposedEvents: true
|
|
||||||
|
|
||||||
onClicked: rekt.clicked()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
Label {
|
|
||||||
property bool coloredBackground
|
|
||||||
|
|
||||||
color: coloredBackground ? "white": Material.foreground
|
|
||||||
|
|
||||||
wrapMode: Label.Wrap
|
|
||||||
linkColor: coloredBackground ? "white" : Material.accent
|
|
||||||
textFormat: Text.RichText
|
|
||||||
|
|
||||||
onLinkActivated: Qt.openUrlExternally(link)
|
|
||||||
}
|
|
|
@ -1,4 +1,6 @@
|
||||||
module Spectral.Component.Timeline
|
module Spectral.Component.Timeline
|
||||||
MessageDelegate 2.0 MessageDelegate.qml
|
MessageDelegate 2.0 MessageDelegate.qml
|
||||||
StateDelegate 2.0 StateDelegate.qml
|
StateDelegate 2.0 StateDelegate.qml
|
||||||
|
SectionDelegate 2.0 SectionDelegate.qml
|
||||||
|
ImageDelegate 2.0 ImageDelegate.qml
|
||||||
|
FileDelegate 2.0 FileDelegate.qml
|
||||||
|
|
|
@ -6,3 +6,4 @@ ScrollHelper 2.0 ScrollHelper.qml
|
||||||
AutoListView 2.0 AutoListView.qml
|
AutoListView 2.0 AutoListView.qml
|
||||||
AutoTextField 2.0 AutoTextField.qml
|
AutoTextField 2.0 AutoTextField.qml
|
||||||
SplitView 2.0 SplitView.qml
|
SplitView 2.0 SplitView.qml
|
||||||
|
Avatar 2.0 Avatar.qml
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: item
|
||||||
|
|
||||||
|
property alias source: mask.source
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: circleMask
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
smooth: true
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
radius: Math.max(width/2, height/2)
|
||||||
|
}
|
||||||
|
|
||||||
|
OpacityMask {
|
||||||
|
id: mask
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
maskSource: circleMask
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtGraphicalEffects 1.0
|
import QtGraphicalEffects 1.0
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
|
@ -7,7 +7,8 @@ import QtGraphicalEffects 1.0
|
||||||
Item {
|
Item {
|
||||||
id: effect
|
id: effect
|
||||||
|
|
||||||
property variant source
|
property var source
|
||||||
|
readonly property Item sourceItem: source.sourceItem
|
||||||
|
|
||||||
property int elevation: 0
|
property int elevation: 0
|
||||||
|
|
||||||
|
@ -134,7 +135,7 @@ Item {
|
||||||
glowRadius: modelData.blur/2
|
glowRadius: modelData.blur/2
|
||||||
spread: 0.05
|
spread: 0.05
|
||||||
color: _shadowColors[index]
|
color: _shadowColors[index]
|
||||||
cornerRadius: modelData.blur + (effect.source.radius || 0)
|
cornerRadius: modelData.blur + (effect.sourceItem.radius || 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,239 @@
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtGraphicalEffects 1.0
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
|
AutoMouseArea {
|
||||||
|
id: ripple
|
||||||
|
|
||||||
|
property color color: MSettings.darkTheme ? Qt.rgba(255, 255, 255, 0.16) : Qt.rgba(0, 0, 0, 0.08)
|
||||||
|
property bool circular: false
|
||||||
|
property bool centered: false
|
||||||
|
property bool focused
|
||||||
|
property color focusColor: "transparent"
|
||||||
|
property int focusWidth: width - 32
|
||||||
|
property Item control
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: control
|
||||||
|
|
||||||
|
onPressedChanged: {
|
||||||
|
if (!control.pressed)
|
||||||
|
__private.removeLastCircle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onPressed: {
|
||||||
|
__private.createTapCircle(mouse.x, mouse.y)
|
||||||
|
|
||||||
|
if (control)
|
||||||
|
mouse.accepted = false
|
||||||
|
}
|
||||||
|
|
||||||
|
onReleased: __private.removeLastCircle()
|
||||||
|
onCanceled: __private.removeLastCircle()
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
id: __private
|
||||||
|
|
||||||
|
property int startRadius: 0
|
||||||
|
property int endRadius
|
||||||
|
property bool showFocus: true
|
||||||
|
|
||||||
|
property Item lastCircle
|
||||||
|
|
||||||
|
function createTapCircle(x, y) {
|
||||||
|
endRadius = centered ? width/2 : radius(x, y) + 5
|
||||||
|
showFocus = false
|
||||||
|
|
||||||
|
lastCircle = tapCircle.createObject(ripple, {
|
||||||
|
"circleX": centered ? width/2 : x,
|
||||||
|
"circleY": centered ? height/2 : y
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeLastCircle() {
|
||||||
|
if (lastCircle)
|
||||||
|
lastCircle.removeCircle()
|
||||||
|
}
|
||||||
|
|
||||||
|
function radius(x, y) {
|
||||||
|
var dist1 = Math.max(dist(x, y, 0, 0), dist(x, y, width, height))
|
||||||
|
var dist2 = Math.max(dist(x, y, width, 0), dist(x, y, 0, height))
|
||||||
|
|
||||||
|
return Math.max(dist1, dist2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function dist(x1, y1, x2, y2) {
|
||||||
|
var distX = x2 - x1
|
||||||
|
var distY = y2 - y1
|
||||||
|
|
||||||
|
return Math.sqrt(distX * distX + distY * distY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: focusBackground
|
||||||
|
objectName: "focusBackground"
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
color: Qt.rgba(0,0,0,0.2)
|
||||||
|
|
||||||
|
opacity: __private.showFocus && focused ? 1 : 0
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: focusCircle
|
||||||
|
objectName: "focusRipple"
|
||||||
|
|
||||||
|
property bool focusedState
|
||||||
|
|
||||||
|
x: (parent.width - width)/2
|
||||||
|
y: (parent.height - height)/2
|
||||||
|
|
||||||
|
width: focused
|
||||||
|
? focusedState ? focusWidth
|
||||||
|
: Math.min(parent.width - 8, focusWidth + 12)
|
||||||
|
: parent.width/5
|
||||||
|
height: width
|
||||||
|
|
||||||
|
radius: width/2
|
||||||
|
|
||||||
|
opacity: __private.showFocus && focused ? 1 : 0
|
||||||
|
|
||||||
|
color: focusColor.a === 0 ? Qt.rgba(1,1,1,0.4) : focusColor
|
||||||
|
|
||||||
|
Behavior on opacity {
|
||||||
|
NumberAnimation { duration: 500; easing.type: Easing.InOutQuad }
|
||||||
|
}
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
NumberAnimation { duration: focusTimer.interval; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: focusTimer
|
||||||
|
running: focused
|
||||||
|
repeat: true
|
||||||
|
interval: 800
|
||||||
|
|
||||||
|
onTriggered: focusCircle.focusedState = !focusCircle.focusedState
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: tapCircle
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: circleItem
|
||||||
|
objectName: "tapRipple"
|
||||||
|
|
||||||
|
property bool done
|
||||||
|
|
||||||
|
property real circleX
|
||||||
|
property real circleY
|
||||||
|
|
||||||
|
property bool closed
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
function removeCircle() {
|
||||||
|
done = true
|
||||||
|
|
||||||
|
if (fillSizeAnimation.running) {
|
||||||
|
fillOpacityAnimation.stop()
|
||||||
|
closeAnimation.start()
|
||||||
|
|
||||||
|
circleItem.destroy(500);
|
||||||
|
} else {
|
||||||
|
__private.showFocus = true
|
||||||
|
fadeAnimation.start();
|
||||||
|
|
||||||
|
circleItem.destroy(300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: circleParent
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
visible: !circular
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: circleRectangle
|
||||||
|
|
||||||
|
x: circleItem.circleX - radius
|
||||||
|
y: circleItem.circleY - radius
|
||||||
|
|
||||||
|
width: radius * 2
|
||||||
|
height: radius * 2
|
||||||
|
|
||||||
|
opacity: 0
|
||||||
|
color: ripple.color
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
id: fillSizeAnimation
|
||||||
|
running: true
|
||||||
|
|
||||||
|
target: circleRectangle; property: "radius"; duration: 500;
|
||||||
|
from: __private.startRadius; to: __private.endRadius;
|
||||||
|
easing.type: Easing.InOutQuad
|
||||||
|
|
||||||
|
onStopped: {
|
||||||
|
if (done)
|
||||||
|
__private.showFocus = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
id: fillOpacityAnimation
|
||||||
|
running: true
|
||||||
|
|
||||||
|
target: circleRectangle; property: "opacity"; duration: 300;
|
||||||
|
from: 0; to: 1; easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
id: fadeAnimation
|
||||||
|
|
||||||
|
target: circleRectangle; property: "opacity"; duration: 300;
|
||||||
|
from: 1; to: 0; easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
SequentialAnimation {
|
||||||
|
id: closeAnimation
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: circleRectangle; property: "opacity"; duration: 250;
|
||||||
|
to: 1; easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
|
||||||
|
NumberAnimation {
|
||||||
|
target: circleRectangle; property: "opacity"; duration: 250;
|
||||||
|
from: 1; to: 0; easing.type: Easing.InOutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CircleMask {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: circleParent
|
||||||
|
visible: circular
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
module Spectral.Effect
|
module Spectral.Effect
|
||||||
ElevationEffect 2.0 ElevationEffect.qml
|
ElevationEffect 2.0 ElevationEffect.qml
|
||||||
|
RippleEffect 2.0 RippleEffect.qml
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
pragma Singleton
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
Label {}
|
|
@ -1,5 +1,5 @@
|
||||||
pragma Singleton
|
pragma Singleton
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
|
|
||||||
FontLoader {
|
FontLoader {
|
||||||
source: "qrc:/assets/font/material.ttf"
|
source: "qrc:/assets/font/material.ttf"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module Spectral.Font
|
module Spectral.Font
|
||||||
singleton MaterialFont 0.1 MaterialFont.qml
|
singleton MaterialFont 0.1 MaterialFont.qml
|
||||||
|
singleton CommonFont 0.1 CommonFont.qml
|
||||||
|
|
|
@ -1,53 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
property var row: null
|
|
||||||
property var model: null
|
|
||||||
property string selectedText
|
|
||||||
|
|
||||||
readonly property bool isFile: model && (model.eventType === "video" || model.eventType === "audio" || model.eventType === "file" || model.eventType === "image")
|
|
||||||
|
|
||||||
id: messageContextMenu
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: "View Source"
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
sourceDialog.sourceText = model.toolTip
|
|
||||||
sourceDialog.open()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
visible: isFile
|
|
||||||
height: visible ? undefined : 0
|
|
||||||
text: "Open Externally"
|
|
||||||
|
|
||||||
onTriggered: row.openExternally()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
visible: isFile
|
|
||||||
height: visible ? undefined : 0
|
|
||||||
text: "Save As"
|
|
||||||
|
|
||||||
onTriggered: row.saveFileAs()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
height: visible ? undefined : 0
|
|
||||||
text: "Reply"
|
|
||||||
|
|
||||||
onTriggered: {
|
|
||||||
roomPanelInput.isReply = true
|
|
||||||
roomPanelInput.replyUserID = model.author.id
|
|
||||||
roomPanelInput.replyEventID = model.eventId
|
|
||||||
roomPanelInput.replyContent = selectedText != "" ? selectedText : model.message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
visible: model && model.author === currentRoom.localUser
|
|
||||||
height: visible ? undefined : 0
|
|
||||||
text: "Redact"
|
|
||||||
|
|
||||||
onTriggered: currentRoom.redactEvent(model.eventId)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import Spectral 0.1
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
property var model: null
|
|
||||||
|
|
||||||
id: roomListMenu
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: "Favourite"
|
|
||||||
checkable: true
|
|
||||||
checked: model && model.category === RoomType.Favorite
|
|
||||||
|
|
||||||
onTriggered: model.category === RoomType.Favorite ? model.currentRoom.removeTag("m.favourite") : model.currentRoom.addTag("m.favourite", 1.0)
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Deprioritize"
|
|
||||||
checkable: true
|
|
||||||
checked: model && model.category === RoomType.Deprioritized
|
|
||||||
|
|
||||||
onTriggered: model.category === RoomType.Deprioritized ? model.currentRoom.removeTag("m.lowpriority") : model.currentRoom.addTag("m.lowpriority", 1.0)
|
|
||||||
}
|
|
||||||
MenuSeparator {}
|
|
||||||
MenuItem {
|
|
||||||
text: "Mark as Read"
|
|
||||||
|
|
||||||
onTriggered: model.currentRoom.markAllMessagesAsRead()
|
|
||||||
}
|
|
||||||
MenuItem {
|
|
||||||
text: "Leave Room"
|
|
||||||
|
|
||||||
onTriggered: model.currentRoom.forget()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
module Spectral.Menu
|
|
||||||
MessageContextMenu 2.0 MessageContextMenu.qml
|
|
||||||
RoomContextMenu 2.0 RoomContextMenu.qml
|
|
|
@ -1,27 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
|
|
||||||
LoginForm {
|
|
||||||
loginButton.onClicked: doLogin()
|
|
||||||
|
|
||||||
Shortcut {
|
|
||||||
sequence: "Return"
|
|
||||||
onActivated: doLogin()
|
|
||||||
}
|
|
||||||
|
|
||||||
function doLogin() {
|
|
||||||
if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) {
|
|
||||||
loginButtonTooltip.text = "Server address should start with http(s)://"
|
|
||||||
loginButtonTooltip.open()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
loginButton.text = "Logging in..."
|
|
||||||
loginButton.enabled = false
|
|
||||||
controller.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
|
|
||||||
|
|
||||||
controller.connectionAdded.connect(function(conn) {
|
|
||||||
stackView.pop()
|
|
||||||
accountListView.currentConnection = conn
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,156 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
Page {
|
|
||||||
property var controller
|
|
||||||
|
|
||||||
property alias loginButton: loginButton
|
|
||||||
property alias serverField: serverField
|
|
||||||
property alias usernameField: usernameField
|
|
||||||
property alias passwordField: passwordField
|
|
||||||
property alias loginButtonTooltip: loginButtonTooltip
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Pane {
|
|
||||||
width: parent.width / 2
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
background: Item {
|
|
||||||
Image {
|
|
||||||
id: background
|
|
||||||
anchors.fill: parent
|
|
||||||
source: "qrc:/assets/img/background.jpg"
|
|
||||||
fillMode: Image.PreserveAspectCrop
|
|
||||||
cache: false
|
|
||||||
}
|
|
||||||
|
|
||||||
ColorOverlay {
|
|
||||||
anchors.fill: background
|
|
||||||
source: background
|
|
||||||
color: Material.accent
|
|
||||||
opacity: 0.7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
x: 32
|
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "MATRIX LOGIN."
|
|
||||||
font.pointSize: 28
|
|
||||||
font.bold: true
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "A NEW METHOD OF MESSAGING"
|
|
||||||
font.pointSize: 12
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Pane {
|
|
||||||
width: parent.width / 2
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
padding: 64
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
id: mainCol
|
|
||||||
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: serverField
|
|
||||||
|
|
||||||
leftPadding: 16
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
|
|
||||||
text: "https://matrix.org"
|
|
||||||
placeholderText: "Server"
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
implicitHeight: 48
|
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#242424" : "#eaeaea"
|
|
||||||
border.color: parent.activeFocus ? Material.accent : "transparent"
|
|
||||||
border.width: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: usernameField
|
|
||||||
|
|
||||||
leftPadding: 16
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
|
|
||||||
placeholderText: "Username"
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
implicitHeight: 48
|
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#242424" : "#eaeaea"
|
|
||||||
border.color: parent.activeFocus ? Material.accent : "transparent"
|
|
||||||
border.width: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: passwordField
|
|
||||||
|
|
||||||
leftPadding: 16
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
|
|
||||||
placeholderText: "Password"
|
|
||||||
echoMode: TextInput.Password
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
implicitHeight: 48
|
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#242424" : "#eaeaea"
|
|
||||||
border.color: parent.activeFocus ? Material.accent : "transparent"
|
|
||||||
border.width: 2
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: loginButton
|
|
||||||
|
|
||||||
text: "LOGIN"
|
|
||||||
highlighted: true
|
|
||||||
|
|
||||||
ToolTip {
|
|
||||||
id: loginButtonTooltip
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*##^## Designer {
|
|
||||||
D{i:0;autoSize:true;height:480;width:640}
|
|
||||||
}
|
|
||||||
##^##*/
|
|
|
@ -1,67 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
|
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
import Spectral.Panel 2.0
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
import Spectral.Effect 2.0
|
|
||||||
|
|
||||||
import Spectral 0.1
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
Page {
|
|
||||||
property alias connection: roomListModel.connection
|
|
||||||
property alias enteredRoom: roomListForm.enteredRoom
|
|
||||||
property alias filter: roomListForm.filter
|
|
||||||
|
|
||||||
id: page
|
|
||||||
|
|
||||||
RoomListModel {
|
|
||||||
id: roomListModel
|
|
||||||
|
|
||||||
onNewMessage: if (!window.active) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon, iconPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
SplitView {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
RoomListPanel {
|
|
||||||
width: page.width * 0.35
|
|
||||||
Layout.minimumWidth: 64
|
|
||||||
|
|
||||||
id: roomListForm
|
|
||||||
|
|
||||||
listModel: roomListModel
|
|
||||||
|
|
||||||
onWidthChanged: {
|
|
||||||
if (width < 240) width = 64
|
|
||||||
}
|
|
||||||
|
|
||||||
ElevationEffect {
|
|
||||||
anchors.fill: source
|
|
||||||
z: source.z - 1
|
|
||||||
|
|
||||||
source: parent
|
|
||||||
elevation: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
onLeaveRoom: roomForm.saveReadMarker(room)
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomPanel {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.minimumWidth: 480
|
|
||||||
|
|
||||||
id: roomForm
|
|
||||||
|
|
||||||
currentRoom: roomListForm.enteredRoom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function goToEvent(eventID) {
|
|
||||||
roomForm.goToEvent(eventID)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
|
|
||||||
SettingForm {
|
|
||||||
addAccountButton.onClicked: stackView.push(loginPage)
|
|
||||||
}
|
|
|
@ -1,135 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
|
|
||||||
import Spectral 0.1
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
Column {
|
|
||||||
property bool expanded: false
|
|
||||||
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
width: accountSettingsListView.width
|
|
||||||
height: 64
|
|
||||||
|
|
||||||
Row {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 8
|
|
||||||
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
ImageItem {
|
|
||||||
width: parent.height
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
hint: user.displayName
|
|
||||||
source: user.paintable
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Label {
|
|
||||||
text: user.displayName
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: user.id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: expanded = !expanded
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
width: parent.width - 32
|
|
||||||
height: expanded ? implicitHeight : 0
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
AutoListView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 24
|
|
||||||
|
|
||||||
orientation: ListView.Horizontal
|
|
||||||
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
model: ["#498882", "#42a5f5", "#5c6bc0", "#7e57c2", "#ab47bc", "#ff7043"]
|
|
||||||
|
|
||||||
delegate: Rectangle {
|
|
||||||
width: parent.height
|
|
||||||
height: parent.height
|
|
||||||
radius: width / 2
|
|
||||||
|
|
||||||
color: modelData
|
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onClicked: spectralController.setColor(connection.localUserId, modelData)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Homeserver:"
|
|
||||||
}
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: connection.homeserver
|
|
||||||
readOnly: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Device ID:"
|
|
||||||
}
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: connection.deviceId
|
|
||||||
readOnly: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
spacing: 16
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "Access Token:"
|
|
||||||
}
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: connection.accessToken
|
|
||||||
readOnly: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
highlighted: true
|
|
||||||
text: "Logout"
|
|
||||||
|
|
||||||
onClicked: spectralController.logout(connection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
text: category
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
settingStackView.clear()
|
|
||||||
settingStackView.push([accountForm, generalForm, appearanceForm, aboutForm][form])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,185 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
import Spectral.Effect 2.0
|
|
||||||
|
|
||||||
import Spectral 0.1
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
Page {
|
|
||||||
property alias listModel: accountSettingsListView.model
|
|
||||||
|
|
||||||
property alias addAccountButton: addAccountButton
|
|
||||||
|
|
||||||
implicitWidth: 400
|
|
||||||
implicitHeight: 300
|
|
||||||
|
|
||||||
Page {
|
|
||||||
id: accountForm
|
|
||||||
|
|
||||||
parent: null
|
|
||||||
|
|
||||||
padding: 64
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
AutoListView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: accountSettingsListView
|
|
||||||
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
delegate: SettingAccountDelegate {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Button {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
id: addAccountButton
|
|
||||||
|
|
||||||
text: "Add Account"
|
|
||||||
flat: true
|
|
||||||
highlighted: true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Page {
|
|
||||||
id: generalForm
|
|
||||||
|
|
||||||
parent: null
|
|
||||||
|
|
||||||
padding: 64
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Switch {
|
|
||||||
text: "Use press and hold instead of right click"
|
|
||||||
checked: MSettings.pressAndHold
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.pressAndHold = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
|
||||||
text: "Show tray icon"
|
|
||||||
checked: MSettings.showTray
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.showTray = checked
|
|
||||||
}
|
|
||||||
|
|
||||||
Switch {
|
|
||||||
text: "Confirm on Exit"
|
|
||||||
checked: MSettings.confirmOnExit
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.confirmOnExit = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Page {
|
|
||||||
id: appearanceForm
|
|
||||||
|
|
||||||
parent: null
|
|
||||||
|
|
||||||
padding: 64
|
|
||||||
|
|
||||||
Column {
|
|
||||||
Switch {
|
|
||||||
text: "Dark theme"
|
|
||||||
checked: MSettings.darkTheme
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.darkTheme = checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Page {
|
|
||||||
id: aboutForm
|
|
||||||
|
|
||||||
parent: null
|
|
||||||
|
|
||||||
padding: 64
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
spacing: 16
|
|
||||||
Image {
|
|
||||||
Layout.preferredWidth: 64
|
|
||||||
Layout.preferredHeight: 64
|
|
||||||
|
|
||||||
source: "qrc:/assets/img/icon.png"
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "Spectral, an IM client for the Matrix protocol."
|
|
||||||
}
|
|
||||||
Label {
|
|
||||||
text: "Released under GNU General Public License, version 3."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: 240
|
|
||||||
height: parent.height
|
|
||||||
z: 10
|
|
||||||
|
|
||||||
id: settingDrawer
|
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#323232" : "#f3f3f3"
|
|
||||||
|
|
||||||
layer.enabled: true
|
|
||||||
layer.effect: ElevationEffect {
|
|
||||||
elevation: 4
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
Repeater {
|
|
||||||
model: ListModel {
|
|
||||||
ListElement {
|
|
||||||
category: "Accounts"
|
|
||||||
form: 0
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
category: "General"
|
|
||||||
form: 1
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
category: "Appearance"
|
|
||||||
form: 2
|
|
||||||
}
|
|
||||||
ListElement {
|
|
||||||
category: "About"
|
|
||||||
form: 3
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: SettingCategoryDelegate {
|
|
||||||
width: parent.width
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StackView {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.leftMargin: settingDrawer.width
|
|
||||||
|
|
||||||
id: settingStackView
|
|
||||||
|
|
||||||
initialItem: aboutForm
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*##^## Designer {
|
|
||||||
D{i:0;autoSize:true;height:480;width:640}
|
|
||||||
}
|
|
||||||
##^##*/
|
|
|
@ -1,39 +1,30 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
|
|
||||||
import "qrc:/js/util.js" as Util
|
|
||||||
|
|
||||||
Drawer {
|
Drawer {
|
||||||
property var room
|
property var room
|
||||||
|
|
||||||
id: roomDrawer
|
id: roomDrawer
|
||||||
|
|
||||||
edge: Qt.RightEdge
|
edge: Qt.RightEdge
|
||||||
interactive: false
|
|
||||||
|
|
||||||
ToolButton {
|
|
||||||
contentItem: MaterialIcon { icon: "\ue5c4" }
|
|
||||||
|
|
||||||
onClicked: roomDrawer.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: 32
|
anchors.margins: 32
|
||||||
|
|
||||||
ImageItem {
|
Avatar {
|
||||||
Layout.preferredWidth: 96
|
Layout.preferredWidth: 96
|
||||||
Layout.preferredHeight: 96
|
Layout.preferredHeight: 96
|
||||||
Layout.alignment: Qt.AlignHCenter
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
hint: room ? room.displayName : "No name"
|
hint: room ? room.displayName : "No name"
|
||||||
source: room ? room.paintable : null
|
source: room ? room.avatarMediaId : null
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
|
@ -57,7 +48,7 @@ Drawer {
|
||||||
|
|
||||||
wrapMode: Label.Wrap
|
wrapMode: Label.Wrap
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: room ? room.memberCount + " Members" : "No Member Count"
|
text: room ? room.totalMemberCount + " Members" : "No Member Count"
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
|
@ -124,11 +115,11 @@ Drawer {
|
||||||
anchors.margins: 8
|
anchors.margins: 8
|
||||||
spacing: 12
|
spacing: 12
|
||||||
|
|
||||||
ImageItem {
|
Avatar {
|
||||||
Layout.preferredWidth: height
|
Layout.preferredWidth: height
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
source: paintable
|
source: avatar
|
||||||
hint: name
|
hint: name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,80 +1,81 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
Rectangle {
|
Control {
|
||||||
property alias paintable: headerImage.source
|
property alias avatar: headerImage.source
|
||||||
property alias topic: headerTopicLabel.text
|
property alias topic: headerTopicLabel.text
|
||||||
|
property bool atTop: false
|
||||||
signal clicked()
|
signal clicked()
|
||||||
|
|
||||||
id: header
|
id: header
|
||||||
|
|
||||||
color: Material.accent
|
background: Rectangle {
|
||||||
|
color: Material.background
|
||||||
|
|
||||||
ItemDelegate {
|
opacity: atTop ? 0 : 1
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: ElevationEffect {
|
||||||
|
elevation: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
|
||||||
id: roomHeader
|
spacing: 12
|
||||||
|
|
||||||
onClicked: header.clicked()
|
Avatar {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
RowLayout {
|
id: headerImage
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
|
|
||||||
spacing: 12
|
source: currentRoom.avatarMediaId
|
||||||
|
hint: currentRoom ? currentRoom.displayName : "No name"
|
||||||
|
}
|
||||||
|
|
||||||
ImageItem {
|
ColumnLayout {
|
||||||
Layout.preferredWidth: height
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
id: headerImage
|
visible: parent.width > 64
|
||||||
|
|
||||||
source: currentRoom.paintable
|
Label {
|
||||||
hint: currentRoom ? currentRoom.displayName : "No name"
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
visible: parent.width > 64
|
text: currentRoom ? currentRoom.displayName : ""
|
||||||
|
color: MPalette.foreground
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
Label {
|
Label {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
|
||||||
text: currentRoom ? currentRoom.displayName : ""
|
id: headerTopicLabel
|
||||||
color: "white"
|
|
||||||
font.pointSize: 12
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
color: MPalette.lighter
|
||||||
Layout.fillWidth: true
|
elide: Text.ElideRight
|
||||||
Layout.fillHeight: true
|
wrapMode: Text.NoWrap
|
||||||
|
|
||||||
id: headerTopicLabel
|
|
||||||
|
|
||||||
color: "white"
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgressBar {
|
RippleEffect {
|
||||||
width: parent.width
|
anchors.fill: parent
|
||||||
z: 10
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
Material.accent: "white"
|
onClicked: header.clicked()
|
||||||
visible: currentRoom && currentRoom.busy
|
|
||||||
indeterminate: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
import Spectral 0.1
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
color: MSettings.darkTheme ? "#303030" : "#fafafa"
|
|
||||||
|
|
||||||
AutoMouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
hoverEnabled: miniMode
|
|
||||||
|
|
||||||
onSecondaryClicked: {
|
|
||||||
roomContextMenu.model = model
|
|
||||||
roomContextMenu.popup()
|
|
||||||
}
|
|
||||||
onPrimaryClicked: {
|
|
||||||
if (category === RoomType.Invited) {
|
|
||||||
inviteDialog.currentRoom = currentRoom
|
|
||||||
inviteDialog.open()
|
|
||||||
} else {
|
|
||||||
leaveRoom(enteredRoom)
|
|
||||||
enterRoom(currentRoom)
|
|
||||||
enteredRoom = currentRoom
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ToolTip.visible: miniMode && containsMouse
|
|
||||||
ToolTip.text: name
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
visible: highlightCount > 0 || currentRoom === enteredRoom
|
|
||||||
color: Material.accent
|
|
||||||
opacity: 0.1
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
width: unreadCount > 0 ? 4 : 0
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
color: Material.accent
|
|
||||||
|
|
||||||
Behavior on width {
|
|
||||||
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
ImageItem {
|
|
||||||
id: imageItem
|
|
||||||
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
source: paintable
|
|
||||||
hint: name || "No Name"
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: parent.width > 64
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: name || "No Name"
|
|
||||||
font.pointSize: 12
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm,"")
|
|
||||||
elide: Text.ElideRight
|
|
||||||
wrapMode: Text.NoWrap
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +1,42 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
|
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
|
import Spectral.Component 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
|
import Spectral 0.1
|
||||||
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
import SortFilterProxyModel 0.2
|
import SortFilterProxyModel 0.2
|
||||||
|
|
||||||
RoomListPanelForm {
|
Item {
|
||||||
model: sortedRoomListModel
|
property var controller: null
|
||||||
|
readonly property var user: controller.connection ? controller.connection.localUser : null
|
||||||
|
|
||||||
|
property int filter: 0
|
||||||
|
property var enteredRoom: null
|
||||||
|
property alias errorControl: errorControl
|
||||||
|
|
||||||
|
signal enterRoom(var room)
|
||||||
|
signal leaveRoom(var room)
|
||||||
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
RoomListModel {
|
||||||
|
id: roomListModel
|
||||||
|
|
||||||
|
connection: controller.connection
|
||||||
|
|
||||||
|
onNewMessage: if (!window.active && MSettings.showNotification) spectralController.postNotification(roomId, eventId, roomName, senderName, text, icon)
|
||||||
|
}
|
||||||
|
|
||||||
SortFilterProxyModel {
|
SortFilterProxyModel {
|
||||||
id: sortedRoomListModel
|
id: sortedRoomListModel
|
||||||
|
|
||||||
sourceModel: listModel
|
sourceModel: roomListModel
|
||||||
|
|
||||||
proxyRoles: ExpressionRole {
|
proxyRoles: ExpressionRole {
|
||||||
name: "display"
|
name: "display"
|
||||||
|
@ -53,9 +80,841 @@ RoomListPanelForm {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
Shortcut {
|
Drawer {
|
||||||
sequence: StandardKey.Find
|
width: Math.max(root.width, 400)
|
||||||
onActivated: searchField.forceActiveFocus()
|
height: root.height
|
||||||
|
|
||||||
|
id: drawer
|
||||||
|
|
||||||
|
edge: Qt.LeftEdge
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: mainPage
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
readonly property string title: "Main"
|
||||||
|
|
||||||
|
id: mainColumn
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 330
|
||||||
|
|
||||||
|
padding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 200
|
||||||
|
Layout.preferredHeight: 200
|
||||||
|
Layout.margins: 12
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
source: root.user ? root.user.avatarMediaId : null
|
||||||
|
hint: root.user ? root.user.displayName : "?"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
text: root.user ? root.user.displayName : "No Name"
|
||||||
|
color: "white"
|
||||||
|
font.pixelSize: 22
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
text: root.user ? root.user.id : "@example:matrix.org"
|
||||||
|
color: "white"
|
||||||
|
opacity: 0.7
|
||||||
|
font.pixelSize: 13
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle { color: Material.primary }
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onClicked: stackView.push(userPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: mainColumn.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: AccountListModel {
|
||||||
|
controller: spectralController
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: user.displayName
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
controller.connection = connection
|
||||||
|
drawer.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: "Add Account"
|
||||||
|
|
||||||
|
onClicked: loginDialog.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 1
|
||||||
|
|
||||||
|
color: MSettings.darkTheme ? "#424242" : "#e7ebeb"
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: "Settings"
|
||||||
|
|
||||||
|
onClicked: stackView.push(settingsPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: "Logout"
|
||||||
|
|
||||||
|
onClicked: controller.logout(controller.connection)
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: "Exit"
|
||||||
|
|
||||||
|
onClicked: Qt.quit()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: userPage
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
readonly property string title: "User Info"
|
||||||
|
|
||||||
|
id: main
|
||||||
|
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: main.width
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
padding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: "Matrix ID"
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: root.user.id
|
||||||
|
color: "#5B7480"
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
padding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: "Name"
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: root.user.name
|
||||||
|
color: "#5B7480"
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
padding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: "Avatar"
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: root.user.avatarMediaId
|
||||||
|
color: "#5B7480"
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
padding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: "Server"
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: root.controller.connection.accessToken
|
||||||
|
color: "#5B7480"
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
padding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: "Device"
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: root.controller.connection.deviceId
|
||||||
|
color: "#5B7480"
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
padding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: "Token"
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: root.controller.connection.accessToken
|
||||||
|
color: "#5B7480"
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: settingsPage
|
||||||
|
|
||||||
|
ScrollView {
|
||||||
|
readonly property string title: "Settings"
|
||||||
|
|
||||||
|
id: main
|
||||||
|
|
||||||
|
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
padding: 32
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
width: main.width - 64
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
text: "Dark theme"
|
||||||
|
checked: MSettings.darkTheme
|
||||||
|
|
||||||
|
onCheckedChanged: MSettings.darkTheme = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
text: "Show notifications"
|
||||||
|
checked: MSettings.showNotification
|
||||||
|
|
||||||
|
onCheckedChanged: MSettings.showNotification = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
text: "Use press and hold instead of right click"
|
||||||
|
checked: MSettings.pressAndHold
|
||||||
|
|
||||||
|
onCheckedChanged: MSettings.pressAndHold = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
text: "Show tray icon"
|
||||||
|
checked: MSettings.showTray
|
||||||
|
|
||||||
|
onCheckedChanged: MSettings.showTray = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
text: "Enable timeline background"
|
||||||
|
checked: MSettings.enableTimelineBackground
|
||||||
|
|
||||||
|
onCheckedChanged: MSettings.enableTimelineBackground = checked
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
Label {
|
||||||
|
text: "DPI"
|
||||||
|
}
|
||||||
|
|
||||||
|
Slider {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
value: controller.dpi()
|
||||||
|
from: 100
|
||||||
|
to: 300
|
||||||
|
stepSize: 25
|
||||||
|
snapMode: Slider.SnapAlways
|
||||||
|
|
||||||
|
ToolTip.visible: pressed
|
||||||
|
ToolTip.text: value
|
||||||
|
|
||||||
|
onMoved: controller.setDpi(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 64
|
||||||
|
|
||||||
|
visible: stackView.depth > 1
|
||||||
|
|
||||||
|
color: Material.primary
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 4
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
icon: "\ue5c4"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: stackView.pop()
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: stackView.currentItem.title
|
||||||
|
color: "white"
|
||||||
|
font.pixelSize: 18
|
||||||
|
elide: Label.ElideRight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
id: stackView
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
initialItem: mainPage
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 64
|
||||||
|
|
||||||
|
id: roomListHeader
|
||||||
|
|
||||||
|
topPadding: 12
|
||||||
|
bottomPadding: 12
|
||||||
|
leftPadding: 12
|
||||||
|
rightPadding: 18
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
visible: !searchField.active
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
icon: {
|
||||||
|
switch (filter) {
|
||||||
|
case 0: return "\ue8b6"
|
||||||
|
case 1: return "\ue7f5"
|
||||||
|
case 2: return "\ue7ff"
|
||||||
|
case 3: return "\ue7fc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: filterMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "All"
|
||||||
|
|
||||||
|
onClicked: filter = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuSeparator {}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "New"
|
||||||
|
|
||||||
|
onClicked: filter = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "People"
|
||||||
|
|
||||||
|
onClicked: filter = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Group"
|
||||||
|
|
||||||
|
onClicked: filter = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: filterMenu.popup()
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
visible: searchField.active
|
||||||
|
|
||||||
|
contentItem: MaterialIcon { icon: "\ue5cd" }
|
||||||
|
|
||||||
|
onClicked: searchField.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoTextField {
|
||||||
|
readonly property bool active: text
|
||||||
|
readonly property bool isRoom: text.match(/#.*:.*\..*/g) || text.match(/!.*:.*\..*/g)
|
||||||
|
readonly property bool isUser: text.match(/@.*:.*\..*/g)
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
id: searchField
|
||||||
|
|
||||||
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
placeholderText: "Search..."
|
||||||
|
color: MPalette.lighter
|
||||||
|
|
||||||
|
background: Item {}
|
||||||
|
}
|
||||||
|
|
||||||
|
ItemDelegate {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
visible: searchField.isRoom || searchField.isUser
|
||||||
|
|
||||||
|
contentItem: MaterialIcon { icon: "\ue145" }
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (searchField.isRoom) {
|
||||||
|
controller.joinRoom(controller.connection, searchField.text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (searchField.isUser) {
|
||||||
|
controller.createDirectChat(controller.connection, searchField.text)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|
||||||
|
visible: !searchField.active
|
||||||
|
|
||||||
|
source: root.user ? root.user.avatarMediaId : null
|
||||||
|
hint: root.user ? root.user.displayName : "?"
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: drawer.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Material.background
|
||||||
|
|
||||||
|
opacity: listView.atYBeginning ? 0 : 1
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: ElevationEffect {
|
||||||
|
elevation: 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
property string error: ""
|
||||||
|
property string detail: ""
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: errorControl
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
topPadding: 16
|
||||||
|
bottomPadding: 16
|
||||||
|
leftPadding: 24
|
||||||
|
rightPadding: 24
|
||||||
|
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: errorControl.error
|
||||||
|
font.pixelSize: 16
|
||||||
|
color: "white"
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: errorControl.detail
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "white"
|
||||||
|
opacity: 0.6
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: "#273338"
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onClicked: errorControl.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoListView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
id: listView
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
model: sortedRoomListModel
|
||||||
|
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
|
||||||
|
delegate: Item {
|
||||||
|
width: listView.width
|
||||||
|
height: 64
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
visible: currentRoom === enteredRoom
|
||||||
|
color: Material.accent
|
||||||
|
opacity: 0.1
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: unreadCount > 0 ? 4 : 0
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
color: Material.accent
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 12
|
||||||
|
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
source: avatar
|
||||||
|
hint: name || "No Name"
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: name || "No Name"
|
||||||
|
color: MPalette.foreground
|
||||||
|
font.pixelSize: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
text: (lastEvent == "" ? topic : lastEvent).replace(/(\r\n\t|\n|\r\t)/gm,"")
|
||||||
|
color: MPalette.lighter
|
||||||
|
font.pixelSize: 13
|
||||||
|
elide: Text.ElideRight
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
visible: notificationCount > 0 && highlightCount == 0
|
||||||
|
color: "white"
|
||||||
|
text: notificationCount
|
||||||
|
leftPadding: 12
|
||||||
|
rightPadding: 12
|
||||||
|
topPadding: 4
|
||||||
|
bottomPadding: 4
|
||||||
|
font.bold: true
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: height / 2
|
||||||
|
color: MPalette.lighter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
visible: highlightCount > 0
|
||||||
|
color: "white"
|
||||||
|
text: highlightCount
|
||||||
|
leftPadding: 12
|
||||||
|
rightPadding: 12
|
||||||
|
topPadding: 4
|
||||||
|
bottomPadding: 4
|
||||||
|
font.bold: true
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
radius: height / 2
|
||||||
|
color: MPalette.accent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RippleEffect {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onSecondaryClicked: roomContextMenu.popup()
|
||||||
|
onPrimaryClicked: {
|
||||||
|
if (category === RoomType.Invited) {
|
||||||
|
inviteDialog.currentRoom = currentRoom
|
||||||
|
inviteDialog.open()
|
||||||
|
} else {
|
||||||
|
if (enteredRoom) {
|
||||||
|
enteredRoom.displayed = false
|
||||||
|
leaveRoom(enteredRoom)
|
||||||
|
}
|
||||||
|
currentRoom.displayed = true
|
||||||
|
enterRoom(currentRoom)
|
||||||
|
enteredRoom = currentRoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Menu {
|
||||||
|
id: roomContextMenu
|
||||||
|
|
||||||
|
MenuItem {
|
||||||
|
text: "Favourite"
|
||||||
|
checkable: true
|
||||||
|
checked: category === RoomType.Favorite
|
||||||
|
|
||||||
|
onTriggered: category === RoomType.Favorite ? currentRoom.removeTag("m.favourite") : currentRoom.addTag("m.favourite", 1.0)
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Deprioritize"
|
||||||
|
checkable: true
|
||||||
|
checked: category === RoomType.Deprioritized
|
||||||
|
|
||||||
|
onTriggered: category === RoomType.Deprioritized ? currentRoom.removeTag("m.lowpriority") : currentRoom.addTag("m.lowpriority", 1.0)
|
||||||
|
}
|
||||||
|
MenuSeparator {}
|
||||||
|
MenuItem {
|
||||||
|
text: "Mark as Read"
|
||||||
|
|
||||||
|
onTriggered: currentRoom.markAllMessagesAsRead()
|
||||||
|
}
|
||||||
|
MenuItem {
|
||||||
|
text: "Leave Room"
|
||||||
|
|
||||||
|
onTriggered: currentRoom.forget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
section.property: "display"
|
||||||
|
section.criteria: ViewSection.FullString
|
||||||
|
section.delegate: Label {
|
||||||
|
width: parent.width
|
||||||
|
height: 24
|
||||||
|
|
||||||
|
text: section
|
||||||
|
color: MPalette.lighter
|
||||||
|
leftPadding: 16
|
||||||
|
elide: Text.ElideRight
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
|
@ -70,11 +929,30 @@ RoomListPanelForm {
|
||||||
|
|
||||||
title: "Action Required"
|
title: "Action Required"
|
||||||
modal: true
|
modal: true
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
||||||
|
|
||||||
contentItem: Label { text: "Accept this invitation?" }
|
contentItem: Label { text: "Accept this invitation?" }
|
||||||
|
|
||||||
onAccepted: currentRoom.acceptInvitation()
|
footer: DialogButtonBox {
|
||||||
onRejected: currentRoom.forget()
|
Button {
|
||||||
|
text: "Accept"
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
onClicked: currentRoom.acceptInvitation()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Reject"
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
onClicked: currentRoom.forget()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Cancel"
|
||||||
|
flat: true
|
||||||
|
|
||||||
|
onClicked: inviteDialog.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,138 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
import QtQml.Models 2.3
|
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
import Spectral.Menu 2.0
|
|
||||||
import Spectral.Effect 2.0
|
|
||||||
|
|
||||||
import Spectral 0.1
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
import SortFilterProxyModel 0.2
|
|
||||||
|
|
||||||
import "qrc:/js/util.js" as Util
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
property var listModel
|
|
||||||
property int filter: 0
|
|
||||||
property var enteredRoom: null
|
|
||||||
|
|
||||||
property alias searchField: searchField
|
|
||||||
property alias model: listView.model
|
|
||||||
|
|
||||||
property bool miniMode: width == 64
|
|
||||||
|
|
||||||
signal enterRoom(var room)
|
|
||||||
signal leaveRoom(var room)
|
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#323232" : "#f3f3f3"
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: miniMode ? "Empty" : "Here? No, not here."
|
|
||||||
anchors.centerIn: parent
|
|
||||||
visible: listView.count === 0
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
Layout.margins: 12
|
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#303030" : "#fafafa"
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
visible: !miniMode && !searchField.text
|
|
||||||
|
|
||||||
icon: "\ue8b6"
|
|
||||||
color: "grey"
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
visible: !miniMode && searchField.text
|
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
|
||||||
icon: "\ue5cd"
|
|
||||||
color: "grey"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: searchField.text = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoTextField {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: searchField
|
|
||||||
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
placeholderText: "Search..."
|
|
||||||
|
|
||||||
background: Item {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoListView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: listView
|
|
||||||
|
|
||||||
spacing: 1
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: RoomListDelegate {
|
|
||||||
width: parent.width
|
|
||||||
height: 64
|
|
||||||
}
|
|
||||||
|
|
||||||
section.property: "display"
|
|
||||||
section.criteria: ViewSection.FullString
|
|
||||||
section.delegate: Label {
|
|
||||||
width: parent.width
|
|
||||||
height: 24
|
|
||||||
|
|
||||||
text: section
|
|
||||||
color: "grey"
|
|
||||||
leftPadding: miniMode ? undefined : 16
|
|
||||||
elide: Text.ElideRight
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
horizontalAlignment: miniMode ? Text.AlignHCenter : undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomContextMenu {
|
|
||||||
id: roomContextMenu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*##^## Designer {
|
|
||||||
D{i:0;autoSize:true;height:480;width:640}
|
|
||||||
}
|
|
||||||
##^##*/
|
|
|
@ -1,50 +1,425 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls 2.12
|
||||||
|
import QtQuick.Layouts 1.12
|
||||||
|
import QtQuick.Controls.Material 2.12
|
||||||
|
import Qt.labs.qmlmodels 1.0
|
||||||
|
|
||||||
RoomPanelForm {
|
import Spectral.Component 2.0
|
||||||
roomHeader.paintable: currentRoom ? currentRoom.paintable : null
|
import Spectral.Component.Emoji 2.0
|
||||||
roomHeader.topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : ""
|
import Spectral.Component.Timeline 2.0
|
||||||
roomHeader.onClicked: roomDrawer.open()
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
sortedMessageEventModel.onModelReset: {
|
import Spectral 0.1
|
||||||
if (currentRoom) {
|
import Spectral.Setting 0.1
|
||||||
var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex())
|
import SortFilterProxyModel 0.2
|
||||||
messageListView.currentIndex = lastScrollPosition
|
|
||||||
if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize < 20)
|
Item {
|
||||||
currentRoom.getPreviousContent(100)
|
property var currentRoom: null
|
||||||
}
|
|
||||||
|
id: root
|
||||||
|
|
||||||
|
MessageEventModel {
|
||||||
|
id: messageEventModel
|
||||||
|
room: currentRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
messageListView {
|
RoomDrawer {
|
||||||
property int largestVisibleIndex: messageListView.count > 0 ? messageListView.indexAt(messageListView.contentX, messageListView.contentY + messageListView.height - 1) : -1
|
width: Math.min(root.width * 0.7, 480)
|
||||||
|
height: root.height
|
||||||
|
|
||||||
onContentYChanged: {
|
id: roomDrawer
|
||||||
if(currentRoom && messageListView.contentY - 5000 < messageListView.originY)
|
|
||||||
currentRoom.getPreviousContent(50);
|
room: currentRoom
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: !currentRoom
|
||||||
|
text: "Please choose a room."
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
visible: currentRoom && MSettings.enableTimelineBackground
|
||||||
|
|
||||||
|
source: MSettings.timelineBackground || MSettings.darkTheme ? "qrc:/assets/img/roompanel-dark.svg" : "qrc:/assets/img/roompanel.svg"
|
||||||
|
fillMode: Image.PreserveAspectCrop
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
visible: currentRoom
|
||||||
|
|
||||||
|
RoomHeader {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 64
|
||||||
|
z: 10
|
||||||
|
|
||||||
|
id: roomHeader
|
||||||
|
|
||||||
|
avatar: currentRoom ? currentRoom.avatarMediaId : ""
|
||||||
|
topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : ""
|
||||||
|
atTop: messageListView.atYBeginning
|
||||||
|
|
||||||
|
onClicked: roomDrawer.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMovementEnded: currentRoom.saveViewport(sortedMessageEventModel.mapToSource(messageListView.indexAt(messageListView.contentX, messageListView.contentY)), sortedMessageEventModel.mapToSource(largestVisibleIndex))
|
ColumnLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
Layout.maximumWidth: 960
|
||||||
|
Layout.leftMargin: 16
|
||||||
|
Layout.rightMargin: 16
|
||||||
|
Layout.bottomMargin: 16
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
|
||||||
displaced: Transition {
|
spacing: 16
|
||||||
NumberAnimation {
|
|
||||||
property: "y"; duration: 200
|
AutoListView {
|
||||||
easing.type: Easing.OutQuad
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
id: messageListView
|
||||||
|
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
displayMarginBeginning: 100
|
||||||
|
displayMarginEnd: 100
|
||||||
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
|
highlightMoveDuration: 500
|
||||||
|
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
model: SortFilterProxyModel {
|
||||||
|
id: sortedMessageEventModel
|
||||||
|
|
||||||
|
sourceModel: messageEventModel
|
||||||
|
|
||||||
|
filters: ExpressionFilter {
|
||||||
|
expression: marks !== 0x10 && eventType !== "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
onModelReset: {
|
||||||
|
if (currentRoom) {
|
||||||
|
var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex())
|
||||||
|
messageListView.currentIndex = lastScrollPosition
|
||||||
|
if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize < 20)
|
||||||
|
currentRoom.getPreviousContent(50)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property int largestVisibleIndex: count > 0 ? indexAt(contentX, contentY + height - 1) : -1
|
||||||
|
|
||||||
|
onContentYChanged: {
|
||||||
|
if(currentRoom && contentY - 5000 < originY)
|
||||||
|
currentRoom.getPreviousContent(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
displaced: Transition {
|
||||||
|
NumberAnimation {
|
||||||
|
property: "y"; duration: 200
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: DelegateChooser {
|
||||||
|
role: "eventType"
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "state"
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: messageListView.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SectionDelegate {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
|
}
|
||||||
|
|
||||||
|
StateDelegate {
|
||||||
|
Layout.maximumWidth: parent.width
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "emote"
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: messageListView.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SectionDelegate {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
|
}
|
||||||
|
|
||||||
|
StateDelegate {
|
||||||
|
Layout.maximumWidth: parent.width
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "message"
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: messageListView.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SectionDelegate {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDelegate {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "notice"
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: messageListView.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SectionDelegate {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageDelegate {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "image"
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: messageListView.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SectionDelegate {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
|
}
|
||||||
|
|
||||||
|
ImageDelegate {
|
||||||
|
Layout.maximumWidth: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DelegateChoice {
|
||||||
|
roleValue: "file"
|
||||||
|
delegate: ColumnLayout {
|
||||||
|
width: messageListView.width
|
||||||
|
spacing: 4
|
||||||
|
|
||||||
|
SectionDelegate {
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
Layout.margins: 16
|
||||||
|
|
||||||
|
visible: section !== aboveSection || Math.abs(time - aboveTime) > 600000
|
||||||
|
}
|
||||||
|
|
||||||
|
FileDelegate {
|
||||||
|
Layout.maximumWidth: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RoundButton {
|
||||||
|
width: 64
|
||||||
|
height: 64
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
|
||||||
|
id: goBottomFab
|
||||||
|
|
||||||
|
visible: currentRoom && currentRoom.hasUnreadMessages
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
icon: "\ue316"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
Material.background: Material.accent
|
||||||
|
|
||||||
|
onClicked: goToEvent(currentRoom.readMarkerEventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
RoundButton {
|
||||||
|
width: 64
|
||||||
|
height: 64
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
|
||||||
|
id: goTopFab
|
||||||
|
|
||||||
|
visible: !messageListView.atYEnd
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
icon: "\ue313"
|
||||||
|
color: "white"
|
||||||
|
}
|
||||||
|
|
||||||
|
Material.background: Material.accent
|
||||||
|
|
||||||
|
onClicked: messageListView.positionViewAtBeginning()
|
||||||
|
}
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
property string sourceText
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 480
|
||||||
|
|
||||||
|
id: sourceDialog
|
||||||
|
|
||||||
|
parent: ApplicationWindow.overlay
|
||||||
|
|
||||||
|
padding: 16
|
||||||
|
|
||||||
|
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
||||||
|
|
||||||
|
contentItem: ScrollView {
|
||||||
|
clip: true
|
||||||
|
TextArea {
|
||||||
|
readOnly: true
|
||||||
|
selectByMouse: true
|
||||||
|
|
||||||
|
text: sourceDialog.sourceText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Popup {
|
||||||
|
property alias listModel: readMarkerListView.model
|
||||||
|
|
||||||
|
x: (window.width - width) / 2
|
||||||
|
y: (window.height - height) / 2
|
||||||
|
width: 320
|
||||||
|
|
||||||
|
id: readMarkerDialog
|
||||||
|
|
||||||
|
parent: ApplicationWindow.overlay
|
||||||
|
|
||||||
|
modal: true
|
||||||
|
padding: 16
|
||||||
|
|
||||||
|
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
||||||
|
|
||||||
|
contentItem: AutoListView {
|
||||||
|
implicitHeight: Math.min(window.height - 64,
|
||||||
|
readMarkerListView.contentHeight)
|
||||||
|
|
||||||
|
id: readMarkerListView
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
boundsBehavior: Flickable.DragOverBounds
|
||||||
|
|
||||||
|
delegate: ItemDelegate {
|
||||||
|
width: parent.width
|
||||||
|
height: 48
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.margins: 8
|
||||||
|
spacing: 12
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: height
|
||||||
|
Layout.fillHeight: true
|
||||||
|
|
||||||
|
source: modelData.avatar
|
||||||
|
hint: modelData.displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: modelData.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ScrollBar.vertical: ScrollBar {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Control {
|
||||||
|
Layout.maximumWidth: parent.width * 0.8
|
||||||
|
|
||||||
|
visible: currentRoom && currentRoom.hasUsersTyping
|
||||||
|
padding: 8
|
||||||
|
|
||||||
|
contentItem: RowLayout {
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: currentRoom && currentRoom.hasUsersTyping ? currentRoom.usersTyping : null
|
||||||
|
|
||||||
|
delegate: Avatar {
|
||||||
|
Layout.preferredWidth: 24
|
||||||
|
Layout.preferredHeight: 24
|
||||||
|
|
||||||
|
source: modelData.avatarMediaId
|
||||||
|
hint: modelData.displayName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: MPalette.banner
|
||||||
|
radius: height / 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomPanelInput {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: roomPanelInput
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
goBottomFab.onClicked: goToEvent(currentRoom.readMarkerEventId)
|
|
||||||
goTopFab.onClicked: messageListView.positionViewAtBeginning()
|
|
||||||
|
|
||||||
function goToEvent(eventID) {
|
function goToEvent(eventID) {
|
||||||
var index = messageEventModel.eventIDToIndex(eventID)
|
var index = messageEventModel.eventIDToIndex(eventID)
|
||||||
if (index === -1) return
|
if (index === -1) return
|
||||||
messageListView.currentIndex = -1
|
// messageListView.currentIndex = sortedMessageEventModel.mapFromSource(index)
|
||||||
messageListView.currentIndex = sortedMessageEventModel.mapFromSource(index)
|
messageListView.positionViewAtIndex(sortedMessageEventModel.mapFromSource(index), ListView.Contain)
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveReadMarker(room) {
|
function saveReadMarker(room) {
|
||||||
var readMarker = sortedMessageEventModel.get(messageListView.largestVisibleIndex).eventId
|
var readMarker = sortedMessageEventModel.get(messageListView.largestVisibleIndex).eventId
|
||||||
if (!readMarker) return
|
if (!readMarker) return
|
||||||
room.readMarkerEventId = readMarker
|
room.readMarkerEventId = readMarker
|
||||||
|
currentRoom.saveViewport(sortedMessageEventModel.mapToSource(messageListView.indexAt(messageListView.contentX, messageListView.contentY)), sortedMessageEventModel.mapToSource(messageListView.largestVisibleIndex))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,322 +0,0 @@
|
||||||
import QtQuick 2.9
|
|
||||||
import QtQuick.Controls 2.2
|
|
||||||
import QtQuick.Layouts 1.3
|
|
||||||
import QtQuick.Controls.Material 2.2
|
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
|
||||||
import Spectral.Component.Emoji 2.0
|
|
||||||
import Spectral.Component.Timeline 2.0
|
|
||||||
import Spectral.Menu 2.0
|
|
||||||
import Spectral.Effect 2.0
|
|
||||||
|
|
||||||
import Spectral 0.1
|
|
||||||
import Spectral.Setting 0.1
|
|
||||||
import SortFilterProxyModel 0.2
|
|
||||||
|
|
||||||
import "qrc:/js/md.js" as Markdown
|
|
||||||
import "qrc:/js/util.js" as Util
|
|
||||||
|
|
||||||
Item {
|
|
||||||
property var currentRoom: null
|
|
||||||
|
|
||||||
property alias roomHeader: roomHeader
|
|
||||||
property alias messageListView: messageListView
|
|
||||||
property alias goTopFab: goTopFab
|
|
||||||
property alias goBottomFab: goBottomFab
|
|
||||||
property alias messageEventModel: messageEventModel
|
|
||||||
property alias sortedMessageEventModel: sortedMessageEventModel
|
|
||||||
property alias roomDrawer: roomDrawer
|
|
||||||
|
|
||||||
id: root
|
|
||||||
|
|
||||||
MessageEventModel {
|
|
||||||
id: messageEventModel
|
|
||||||
room: currentRoom
|
|
||||||
}
|
|
||||||
|
|
||||||
RoomDrawer {
|
|
||||||
width: Math.min(root.width * 0.7, 480)
|
|
||||||
height: root.height
|
|
||||||
|
|
||||||
id: roomDrawer
|
|
||||||
|
|
||||||
room: currentRoom
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
anchors.centerIn: parent
|
|
||||||
visible: !currentRoom
|
|
||||||
text: "Please choose a room."
|
|
||||||
}
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
visible: currentRoom
|
|
||||||
|
|
||||||
RoomHeader {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 64
|
|
||||||
z: 10
|
|
||||||
|
|
||||||
id: roomHeader
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoListView {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
|
|
||||||
id: messageListView
|
|
||||||
|
|
||||||
displayMarginBeginning: 40
|
|
||||||
displayMarginEnd: 40
|
|
||||||
verticalLayoutDirection: ListView.BottomToTop
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
|
||||||
|
|
||||||
model: SortFilterProxyModel {
|
|
||||||
id: sortedMessageEventModel
|
|
||||||
|
|
||||||
sourceModel: messageEventModel
|
|
||||||
|
|
||||||
filters: ExpressionFilter {
|
|
||||||
expression: marks !== 0x08 && marks !== 0x10
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: ColumnLayout {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
id: delegateColumn
|
|
||||||
|
|
||||||
spacing: 8
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: section !== aboveSection
|
|
||||||
|
|
||||||
text: section
|
|
||||||
color: "white"
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
leftPadding: 8
|
|
||||||
rightPadding: 8
|
|
||||||
topPadding: 4
|
|
||||||
bottomPadding: 4
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
color: MSettings.darkTheme ? "#484848" : "grey"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageDelegate {
|
|
||||||
visible: eventType === "notice" || eventType === "message"
|
|
||||||
|| eventType === "image" || eventType === "video"
|
|
||||||
|| eventType === "audio" || eventType === "file"
|
|
||||||
}
|
|
||||||
|
|
||||||
StateDelegate {
|
|
||||||
Layout.maximumWidth: messageListView.width * 0.8
|
|
||||||
|
|
||||||
visible: eventType === "emote" || eventType === "state"
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: eventType === "other"
|
|
||||||
|
|
||||||
text: display
|
|
||||||
color: "grey"
|
|
||||||
font.italic: true
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.alignment: Qt.AlignHCenter
|
|
||||||
|
|
||||||
visible: readMarker === true
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 2
|
|
||||||
|
|
||||||
color: Material.accent
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
text: "And Now"
|
|
||||||
color: Material.accent
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 2
|
|
||||||
|
|
||||||
color: Material.accent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RoundButton {
|
|
||||||
width: 64
|
|
||||||
height: 64
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.top: parent.top
|
|
||||||
|
|
||||||
id: goBottomFab
|
|
||||||
|
|
||||||
visible: currentRoom && currentRoom.hasUnreadMessages
|
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue316"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
Material.background: Material.accent
|
|
||||||
}
|
|
||||||
|
|
||||||
RoundButton {
|
|
||||||
width: 64
|
|
||||||
height: 64
|
|
||||||
anchors.right: parent.right
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
id: goTopFab
|
|
||||||
|
|
||||||
visible: !messageListView.atYEnd
|
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue313"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
Material.background: Material.accent
|
|
||||||
}
|
|
||||||
|
|
||||||
MessageContextMenu {
|
|
||||||
id: messageContextMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
Popup {
|
|
||||||
property string sourceText
|
|
||||||
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 480
|
|
||||||
|
|
||||||
id: sourceDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
modal: true
|
|
||||||
|
|
||||||
padding: 16
|
|
||||||
|
|
||||||
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
|
||||||
|
|
||||||
contentItem: ScrollView {
|
|
||||||
TextArea {
|
|
||||||
readOnly: true
|
|
||||||
selectByMouse: true
|
|
||||||
|
|
||||||
text: sourceDialog.sourceText
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Popup {
|
|
||||||
property alias listModel: readMarkerListView.model
|
|
||||||
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 320
|
|
||||||
|
|
||||||
id: readMarkerDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
modal: true
|
|
||||||
padding: 16
|
|
||||||
|
|
||||||
closePolicy: Dialog.CloseOnEscape | Dialog.CloseOnPressOutside
|
|
||||||
|
|
||||||
contentItem: AutoListView {
|
|
||||||
implicitHeight: Math.min(window.height - 64,
|
|
||||||
readMarkerListView.contentHeight)
|
|
||||||
|
|
||||||
id: readMarkerListView
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
boundsBehavior: Flickable.DragOverBounds
|
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
|
||||||
width: parent.width
|
|
||||||
height: 48
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 8
|
|
||||||
spacing: 12
|
|
||||||
|
|
||||||
ImageItem {
|
|
||||||
Layout.preferredWidth: height
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
source: modelData.paintable
|
|
||||||
hint: modelData.displayName
|
|
||||||
}
|
|
||||||
|
|
||||||
Label {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
text: modelData.displayName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollBar.vertical: ScrollBar {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Item {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: 40
|
|
||||||
Layout.leftMargin: 16
|
|
||||||
Layout.rightMargin: 16
|
|
||||||
|
|
||||||
color: Material.background
|
|
||||||
|
|
||||||
RoomPanelInput {
|
|
||||||
anchors.verticalCenter: parent.top
|
|
||||||
|
|
||||||
id: roomPanelInput
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 48
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/*##^## Designer {
|
|
||||||
D{i:0;autoSize:true;height:480;width:640}
|
|
||||||
}
|
|
||||||
##^##*/
|
|
|
@ -1,7 +1,7 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
import Spectral.Component.Emoji 2.0
|
import Spectral.Component.Emoji 2.0
|
||||||
|
@ -10,167 +10,222 @@ import Spectral.Setting 0.1
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
|
|
||||||
import "qrc:/js/md.js" as Markdown
|
Control {
|
||||||
|
property alias isReply: replyItem.visible
|
||||||
Rectangle {
|
property var replyUser
|
||||||
property bool isReply
|
|
||||||
property string replyUserID
|
|
||||||
property string replyEventID
|
property string replyEventID
|
||||||
property string replyContent
|
property string replyContent
|
||||||
|
|
||||||
property bool isAutoCompleting
|
property alias isAutoCompleting: autoCompleteListView.visible
|
||||||
property var autoCompleteModel
|
property var autoCompleteModel
|
||||||
property int autoCompleteBeginPosition
|
property int autoCompleteBeginPosition
|
||||||
property int autoCompleteEndPosition
|
property int autoCompleteEndPosition
|
||||||
|
|
||||||
color: MSettings.darkTheme ? "#303030" : "#fafafa"
|
id: root
|
||||||
|
|
||||||
layer.enabled: true
|
padding: 0
|
||||||
layer.effect: ElevationEffect {
|
|
||||||
elevation: 2
|
background: Rectangle {
|
||||||
|
color: MSettings.darkTheme ? "#303030" : "#fafafa"
|
||||||
|
radius: 24
|
||||||
|
|
||||||
|
layer.enabled: true
|
||||||
|
layer.effect: ElevationEffect {
|
||||||
|
elevation: 2
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Popup {
|
contentItem: ColumnLayout {
|
||||||
x: 0
|
spacing: 0
|
||||||
y: -height - 10
|
|
||||||
width: Math.min(autoCompleteListView.contentWidth, parent.width)
|
|
||||||
height: 36
|
|
||||||
padding: 0
|
|
||||||
|
|
||||||
Material.elevation: 2
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.margins: 8
|
||||||
|
|
||||||
id: autoComplete
|
id: replyItem
|
||||||
|
|
||||||
visible: isAutoCompleting && autoCompleteModel.length !== 0
|
visible: false
|
||||||
|
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
Avatar {
|
||||||
|
Layout.preferredWidth: 32
|
||||||
|
Layout.preferredHeight: 32
|
||||||
|
|
||||||
|
source: replyUser ? replyUser.avatarMediaId : ""
|
||||||
|
hint: replyUser ? replyUser.displayName : "No name"
|
||||||
|
}
|
||||||
|
|
||||||
|
Label {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
text: replyContent
|
||||||
|
font.pixelSize: 16
|
||||||
|
|
||||||
|
wrapMode: Label.Wrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiPicker {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
id: emojiPicker
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
|
||||||
|
textArea: inputField
|
||||||
|
emojiModel: EmojiModel { id: emojiModel }
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.preferredHeight: 36
|
||||||
|
Layout.margins: 8
|
||||||
|
|
||||||
contentItem: ListView {
|
|
||||||
id: autoCompleteListView
|
id: autoCompleteListView
|
||||||
|
|
||||||
|
visible: false
|
||||||
|
|
||||||
model: autoCompleteModel
|
model: autoCompleteModel
|
||||||
|
|
||||||
clip: true
|
clip: true
|
||||||
|
spacing: 4
|
||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
highlightFollowsCurrentItem: true
|
highlightFollowsCurrentItem: true
|
||||||
keyNavigationWraps: true
|
keyNavigationWraps: true
|
||||||
|
|
||||||
highlight: Rectangle {
|
delegate: Control {
|
||||||
color: Material.accent
|
|
||||||
opacity: 0.4
|
|
||||||
}
|
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
|
||||||
property string autoCompleteText: modelData.displayName || modelData.unicode
|
property string autoCompleteText: modelData.displayName || modelData.unicode
|
||||||
property bool isEmoji: modelData.unicode != null
|
property bool isEmoji: modelData.unicode != null
|
||||||
|
readonly property bool highlighted: autoCompleteListView.currentIndex === index
|
||||||
|
|
||||||
height: parent.height
|
height: 36
|
||||||
padding: 4
|
padding: 8
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
visible: !isEmoji
|
||||||
|
color: highlighted ? Material.accent : "transparent"
|
||||||
|
border.color: Material.accent
|
||||||
|
border.width: 2
|
||||||
|
radius: height / 2
|
||||||
|
}
|
||||||
|
|
||||||
contentItem: Row {
|
contentItem: Row {
|
||||||
spacing: 8
|
spacing: 4
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
width: parent.height
|
width: 20
|
||||||
height: parent.height
|
height: 20
|
||||||
visible: isEmoji
|
visible: isEmoji
|
||||||
text: autoCompleteText
|
text: autoCompleteText
|
||||||
font.pointSize: 16
|
font.pixelSize: 24
|
||||||
font.family: "Emoji"
|
font.family: "Emoji"
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
}
|
}
|
||||||
ImageItem {
|
Avatar {
|
||||||
width: parent.height
|
width: 20
|
||||||
height: parent.height
|
height: 20
|
||||||
visible: !isEmoji
|
visible: !isEmoji
|
||||||
source: modelData.paintable || null
|
source: modelData.avatarMediaId || null
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
height: parent.height
|
height: parent.height
|
||||||
visible: !isEmoji
|
visible: !isEmoji
|
||||||
text: autoCompleteText
|
text: autoCompleteText
|
||||||
|
color: highlighted ? "white" : Material.accent
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
MouseArea {
|
||||||
autoCompleteListView.currentIndex = index
|
anchors.fill: parent
|
||||||
inputField.replaceAutoComplete(autoCompleteText)
|
onClicked: {
|
||||||
|
autoCompleteListView.currentIndex = index
|
||||||
|
inputField.replaceAutoComplete(autoCompleteText)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0
|
|
||||||
height: parent.height
|
|
||||||
|
|
||||||
opacity: 0.2
|
|
||||||
color: Material.accent
|
|
||||||
}
|
|
||||||
|
|
||||||
RowLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.preferredWidth: 48
|
|
||||||
Layout.preferredHeight: 48
|
|
||||||
|
|
||||||
id: uploadButton
|
|
||||||
visible: !isReply
|
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
|
||||||
icon: "\ue226"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: currentRoom.chooseAndUploadFile()
|
|
||||||
|
|
||||||
BusyIndicator {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
running: currentRoom && currentRoom.hasFileUploading
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
|
||||||
Layout.preferredWidth: 48
|
|
||||||
Layout.preferredHeight: 48
|
|
||||||
|
|
||||||
id: cancelReplyButton
|
|
||||||
visible: isReply
|
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
|
||||||
icon: "\ue5cd"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: clearReply()
|
|
||||||
}
|
|
||||||
|
|
||||||
ScrollView {
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: 48
|
Layout.preferredHeight: 1
|
||||||
|
Layout.leftMargin: 12
|
||||||
|
Layout.rightMargin: 12
|
||||||
|
|
||||||
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
|
visible: emojiPicker.visible || replyItem.visible || autoCompleteListView.visible
|
||||||
|
|
||||||
clip: true
|
color: MSettings.darkTheme ? "#424242" : "#e7ebeb"
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
|
||||||
|
id: uploadButton
|
||||||
|
visible: !isReply
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
icon: "\ue226"
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: currentRoom.chooseAndUploadFile()
|
||||||
|
|
||||||
|
BusyIndicator {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
running: currentRoom && currentRoom.hasFileUploading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolButton {
|
||||||
|
Layout.preferredWidth: 48
|
||||||
|
Layout.preferredHeight: 48
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
|
||||||
|
id: cancelReplyButton
|
||||||
|
|
||||||
|
visible: isReply
|
||||||
|
|
||||||
|
contentItem: MaterialIcon {
|
||||||
|
icon: "\ue5cd"
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: clearReply()
|
||||||
|
}
|
||||||
|
|
||||||
TextArea {
|
TextArea {
|
||||||
property real progress: 0
|
property real progress: 0
|
||||||
|
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.minimumHeight: 48
|
||||||
|
|
||||||
id: inputField
|
id: inputField
|
||||||
|
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
placeholderText: isReply ? "Reply to " + replyUserID : "Send a Message"
|
placeholderText: "Send a Message"
|
||||||
leftPadding: 16
|
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
bottomPadding: 0
|
bottomPadding: 0
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
verticalAlignment: TextEdit.AlignVCenter
|
verticalAlignment: TextEdit.AlignVCenter
|
||||||
|
|
||||||
text: currentRoom ? currentRoom.cachedInput : ""
|
text: currentRoom != null ? currentRoom.cachedInput : ""
|
||||||
|
|
||||||
background: Item {
|
background: Item {}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: currentRoom && currentRoom.hasFileUploading ? parent.width * currentRoom.fileUploadingProgress / 100 : 0
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
opacity: 0.2
|
||||||
|
color: Material.accent
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
|
@ -193,19 +248,18 @@ Rectangle {
|
||||||
onTriggered: currentRoom.sendTypingNotification(true)
|
onTriggered: currentRoom.sendTypingNotification(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
ToolTip.visible: currentRoom
|
|
||||||
&& currentRoom.hasUsersTyping
|
|
||||||
ToolTip.text: currentRoom ? currentRoom.usersTyping : ""
|
|
||||||
|
|
||||||
Keys.onReturnPressed: {
|
Keys.onReturnPressed: {
|
||||||
if (event.modifiers & Qt.ShiftModifier) {
|
if (event.modifiers & Qt.ShiftModifier) {
|
||||||
insert(cursorPosition, "\n")
|
insert(cursorPosition, "\n")
|
||||||
} else {
|
} else if (text) {
|
||||||
postMessage(text)
|
postMessage(text)
|
||||||
text = ""
|
text = ""
|
||||||
|
closeAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Keys.onEscapePressed: closeAll()
|
||||||
|
|
||||||
Keys.onBacktabPressed: if (isAutoCompleting) autoCompleteListView.decrementCurrentIndex()
|
Keys.onBacktabPressed: if (isAutoCompleting) autoCompleteListView.decrementCurrentIndex()
|
||||||
|
|
||||||
Keys.onTabPressed: {
|
Keys.onTabPressed: {
|
||||||
|
@ -259,8 +313,7 @@ Rectangle {
|
||||||
var PREFIX_MARKDOWN = '/md '
|
var PREFIX_MARKDOWN = '/md '
|
||||||
|
|
||||||
if (isReply) {
|
if (isReply) {
|
||||||
currentRoom.sendReply(replyUserID, replyEventID, replyContent, text)
|
currentRoom.sendReply(replyUser.id, replyEventID, replyContent, text)
|
||||||
clearReply()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -294,44 +347,26 @@ Rectangle {
|
||||||
}
|
}
|
||||||
if (text.indexOf(PREFIX_MARKDOWN) === 0) {
|
if (text.indexOf(PREFIX_MARKDOWN) === 0) {
|
||||||
text = text.substr(PREFIX_MARKDOWN.length)
|
text = text.substr(PREFIX_MARKDOWN.length)
|
||||||
var parsedText = Markdown.markdown_parser(text)
|
currentRoom.postMarkdownText(text)
|
||||||
currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
currentRoom.postPlainText(text)
|
currentRoom.postPlainText(text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ItemDelegate {
|
ToolButton {
|
||||||
Layout.preferredWidth: 48
|
Layout.preferredWidth: 48
|
||||||
Layout.preferredHeight: 48
|
Layout.preferredHeight: 48
|
||||||
|
Layout.alignment: Qt.AlignBottom
|
||||||
|
|
||||||
id: emojiButton
|
id: emojiButton
|
||||||
|
|
||||||
contentItem: MaterialIcon {
|
contentItem: MaterialIcon {
|
||||||
icon: "\ue24e"
|
icon: "\ue24e"
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: emojiPicker.visible ? emojiPicker.close() : emojiPicker.open()
|
|
||||||
|
|
||||||
EmojiPicker {
|
|
||||||
x: -width + parent.width
|
|
||||||
y: -height - 16
|
|
||||||
|
|
||||||
width: 360
|
|
||||||
height: 320
|
|
||||||
|
|
||||||
id: emojiPicker
|
|
||||||
|
|
||||||
emojiModel: EmojiModel {
|
|
||||||
id: emojiModel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Material.elevation: 2
|
onClicked: emojiPicker.visible = !emojiPicker.visible
|
||||||
|
|
||||||
textArea: inputField
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,8 +381,18 @@ Rectangle {
|
||||||
|
|
||||||
function clearReply() {
|
function clearReply() {
|
||||||
isReply = false
|
isReply = false
|
||||||
replyUserID = ""
|
replyUser = null
|
||||||
replyEventID = ""
|
replyEventID = ""
|
||||||
replyContent = ""
|
replyContent = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function focus() {
|
||||||
|
inputField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeAll() {
|
||||||
|
replyItem.visible = false
|
||||||
|
autoCompleteListView.visible = false
|
||||||
|
emojiPicker.visible = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
pragma Singleton
|
||||||
|
import QtQuick 2.12
|
||||||
|
import QtQuick.Controls.Material 2.12
|
||||||
|
|
||||||
|
QtObject {
|
||||||
|
readonly property int theme: MSettings.darkTheme ? Material.Dark : Material.Light
|
||||||
|
|
||||||
|
readonly property color primary: "#344955"
|
||||||
|
readonly property color accent: "#673AB7"
|
||||||
|
readonly property color foreground: MSettings.darkTheme ? "#FFFFFF" : "#1D333E"
|
||||||
|
readonly property color background: MSettings.darkTheme ? "#303030" : "#FFFFFF"
|
||||||
|
readonly property color lighter: MSettings.darkTheme ? "#FFFFFF" : "#5B7480"
|
||||||
|
readonly property color banner: MSettings.darkTheme ? "#404040" : "#F2F3F4"
|
||||||
|
}
|
|
@ -1,11 +1,15 @@
|
||||||
pragma Singleton
|
pragma Singleton
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import Qt.labs.settings 1.0
|
import Qt.labs.settings 1.0
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
|
property bool showNotification: true
|
||||||
|
|
||||||
property bool pressAndHold
|
property bool pressAndHold
|
||||||
property bool showTray: true
|
property bool showTray: true
|
||||||
property bool confirmOnExit: true
|
|
||||||
|
|
||||||
property bool darkTheme
|
property bool darkTheme
|
||||||
|
|
||||||
|
property bool enableTimelineBackground: true
|
||||||
|
property string timelineBackground
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module Spectral.Setting
|
module Spectral.Setting
|
||||||
singleton MSettings 0.1 Setting.qml
|
singleton MSettings 0.1 Setting.qml
|
||||||
|
singleton MPalette 0.1 Palette.qml
|
||||||
|
|
|
@ -0,0 +1,281 @@
|
||||||
|
#include "autolink.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
#include <strings.h>
|
||||||
|
#else
|
||||||
|
#define strncasecmp _strnicmp
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int
|
||||||
|
hoedown_autolink_is_safe(const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
static const size_t valid_uris_count = 6;
|
||||||
|
static const char *valid_uris[] = {
|
||||||
|
"http://", "https://", "/", "#", "ftp://", "mailto:"
|
||||||
|
};
|
||||||
|
static const size_t valid_uris_size[] = { 7, 8, 1, 1, 6, 7 };
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < valid_uris_count; ++i) {
|
||||||
|
size_t len = valid_uris_size[i];
|
||||||
|
|
||||||
|
if (size > len &&
|
||||||
|
strncasecmp((char *)data, valid_uris[i], len) == 0 &&
|
||||||
|
isalnum(data[len]))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
autolink_delim(uint8_t *data, size_t link_end, size_t max_rewind, size_t size)
|
||||||
|
{
|
||||||
|
uint8_t cclose, copen = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < link_end; ++i)
|
||||||
|
if (data[i] == '<') {
|
||||||
|
link_end = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (link_end > 0) {
|
||||||
|
if (strchr("?!.,:", data[link_end - 1]) != NULL)
|
||||||
|
link_end--;
|
||||||
|
|
||||||
|
else if (data[link_end - 1] == ';') {
|
||||||
|
size_t new_end = link_end - 2;
|
||||||
|
|
||||||
|
while (new_end > 0 && isalpha(data[new_end]))
|
||||||
|
new_end--;
|
||||||
|
|
||||||
|
if (new_end < link_end - 2 && data[new_end] == '&')
|
||||||
|
link_end = new_end;
|
||||||
|
else
|
||||||
|
link_end--;
|
||||||
|
}
|
||||||
|
else break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link_end == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
cclose = data[link_end - 1];
|
||||||
|
|
||||||
|
switch (cclose) {
|
||||||
|
case '"': copen = '"'; break;
|
||||||
|
case '\'': copen = '\''; break;
|
||||||
|
case ')': copen = '('; break;
|
||||||
|
case ']': copen = '['; break;
|
||||||
|
case '}': copen = '{'; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (copen != 0) {
|
||||||
|
size_t closing = 0;
|
||||||
|
size_t opening = 0;
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
/* Try to close the final punctuation sign in this same line;
|
||||||
|
* if we managed to close it outside of the URL, that means that it's
|
||||||
|
* not part of the URL. If it closes inside the URL, that means it
|
||||||
|
* is part of the URL.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
*
|
||||||
|
* foo http://www.pokemon.com/Pikachu_(Electric) bar
|
||||||
|
* => http://www.pokemon.com/Pikachu_(Electric)
|
||||||
|
*
|
||||||
|
* foo (http://www.pokemon.com/Pikachu_(Electric)) bar
|
||||||
|
* => http://www.pokemon.com/Pikachu_(Electric)
|
||||||
|
*
|
||||||
|
* foo http://www.pokemon.com/Pikachu_(Electric)) bar
|
||||||
|
* => http://www.pokemon.com/Pikachu_(Electric))
|
||||||
|
*
|
||||||
|
* (foo http://www.pokemon.com/Pikachu_(Electric)) bar
|
||||||
|
* => foo http://www.pokemon.com/Pikachu_(Electric)
|
||||||
|
*/
|
||||||
|
|
||||||
|
while (i < link_end) {
|
||||||
|
if (data[i] == copen)
|
||||||
|
opening++;
|
||||||
|
else if (data[i] == cclose)
|
||||||
|
closing++;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closing != opening)
|
||||||
|
link_end--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return link_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
check_domain(uint8_t *data, size_t size, int allow_short)
|
||||||
|
{
|
||||||
|
size_t i, np = 0;
|
||||||
|
|
||||||
|
if (!isalnum(data[0]))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (i = 1; i < size - 1; ++i) {
|
||||||
|
if (strchr(".:", data[i]) != NULL) np++;
|
||||||
|
else if (!isalnum(data[i]) && data[i] != '-') break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allow_short) {
|
||||||
|
/* We don't need a valid domain in the strict sense (with
|
||||||
|
* least one dot; so just make sure it's composed of valid
|
||||||
|
* domain characters and return the length of the the valid
|
||||||
|
* sequence. */
|
||||||
|
return i;
|
||||||
|
} else {
|
||||||
|
/* a valid domain needs to have at least a dot.
|
||||||
|
* that's as far as we get */
|
||||||
|
return np ? i : 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
hoedown_autolink__www(
|
||||||
|
size_t *rewind_p,
|
||||||
|
hoedown_buffer *link,
|
||||||
|
uint8_t *data,
|
||||||
|
size_t max_rewind,
|
||||||
|
size_t size,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
size_t link_end;
|
||||||
|
|
||||||
|
if (max_rewind > 0 && !ispunct(data[-1]) && !isspace(data[-1]))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (size < 4 || memcmp(data, "www.", strlen("www.")) != 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
link_end = check_domain(data, size, 0);
|
||||||
|
|
||||||
|
if (link_end == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while (link_end < size && !isspace(data[link_end]))
|
||||||
|
link_end++;
|
||||||
|
|
||||||
|
link_end = autolink_delim(data, link_end, max_rewind, size);
|
||||||
|
|
||||||
|
if (link_end == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
hoedown_buffer_put(link, data, link_end);
|
||||||
|
*rewind_p = 0;
|
||||||
|
|
||||||
|
return (int)link_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
hoedown_autolink__email(
|
||||||
|
size_t *rewind_p,
|
||||||
|
hoedown_buffer *link,
|
||||||
|
uint8_t *data,
|
||||||
|
size_t max_rewind,
|
||||||
|
size_t size,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
size_t link_end, rewind;
|
||||||
|
int nb = 0, np = 0;
|
||||||
|
|
||||||
|
for (rewind = 0; rewind < max_rewind; ++rewind) {
|
||||||
|
uint8_t c = data[-1 - rewind];
|
||||||
|
|
||||||
|
if (isalnum(c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (strchr(".+-_", c) != NULL)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rewind == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
for (link_end = 0; link_end < size; ++link_end) {
|
||||||
|
uint8_t c = data[link_end];
|
||||||
|
|
||||||
|
if (isalnum(c))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (c == '@')
|
||||||
|
nb++;
|
||||||
|
else if (c == '.' && link_end < size - 1)
|
||||||
|
np++;
|
||||||
|
else if (c != '-' && c != '_')
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (link_end < 2 || nb != 1 || np == 0 ||
|
||||||
|
!isalpha(data[link_end - 1]))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
link_end = autolink_delim(data, link_end, max_rewind, size);
|
||||||
|
|
||||||
|
if (link_end == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
hoedown_buffer_put(link, data - rewind, link_end + rewind);
|
||||||
|
*rewind_p = rewind;
|
||||||
|
|
||||||
|
return link_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t
|
||||||
|
hoedown_autolink__url(
|
||||||
|
size_t *rewind_p,
|
||||||
|
hoedown_buffer *link,
|
||||||
|
uint8_t *data,
|
||||||
|
size_t max_rewind,
|
||||||
|
size_t size,
|
||||||
|
unsigned int flags)
|
||||||
|
{
|
||||||
|
size_t link_end, rewind = 0, domain_len;
|
||||||
|
|
||||||
|
if (size < 4 || data[1] != '/' || data[2] != '/')
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
while (rewind < max_rewind && isalpha(data[-1 - rewind]))
|
||||||
|
rewind++;
|
||||||
|
|
||||||
|
if (!hoedown_autolink_is_safe(data - rewind, size + rewind))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
link_end = strlen("://");
|
||||||
|
|
||||||
|
domain_len = check_domain(
|
||||||
|
data + link_end,
|
||||||
|
size - link_end,
|
||||||
|
flags & HOEDOWN_AUTOLINK_SHORT_DOMAINS);
|
||||||
|
|
||||||
|
if (domain_len == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
link_end += domain_len;
|
||||||
|
while (link_end < size && !isspace(data[link_end]))
|
||||||
|
link_end++;
|
||||||
|
|
||||||
|
link_end = autolink_delim(data, link_end, max_rewind, size);
|
||||||
|
|
||||||
|
if (link_end == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
hoedown_buffer_put(link, data - rewind, link_end + rewind);
|
||||||
|
*rewind_p = rewind;
|
||||||
|
|
||||||
|
return link_end;
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* autolink.h - versatile autolinker */
|
||||||
|
|
||||||
|
#ifndef HOEDOWN_AUTOLINK_H
|
||||||
|
#define HOEDOWN_AUTOLINK_H
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* CONSTANTS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
typedef enum hoedown_autolink_flags {
|
||||||
|
HOEDOWN_AUTOLINK_SHORT_DOMAINS = (1 << 0)
|
||||||
|
} hoedown_autolink_flags;
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* FUNCTIONS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
/* hoedown_autolink_is_safe: verify that a URL has a safe protocol */
|
||||||
|
int hoedown_autolink_is_safe(const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_autolink__www: search for the next www link in data */
|
||||||
|
size_t hoedown_autolink__www(size_t *rewind_p, hoedown_buffer *link,
|
||||||
|
uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
|
||||||
|
|
||||||
|
/* hoedown_autolink__email: search for the next email in data */
|
||||||
|
size_t hoedown_autolink__email(size_t *rewind_p, hoedown_buffer *link,
|
||||||
|
uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
|
||||||
|
|
||||||
|
/* hoedown_autolink__url: search for the next URL in data */
|
||||||
|
size_t hoedown_autolink__url(size_t *rewind_p, hoedown_buffer *link,
|
||||||
|
uint8_t *data, size_t offset, size_t size, hoedown_autolink_flags flags);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /** HOEDOWN_AUTOLINK_H **/
|
|
@ -0,0 +1,308 @@
|
||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
void *
|
||||||
|
hoedown_malloc(size_t size)
|
||||||
|
{
|
||||||
|
void *ret = malloc(size);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
fprintf(stderr, "Allocation failed.\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
hoedown_calloc(size_t nmemb, size_t size)
|
||||||
|
{
|
||||||
|
void *ret = calloc(nmemb, size);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
fprintf(stderr, "Allocation failed.\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
hoedown_realloc(void *ptr, size_t size)
|
||||||
|
{
|
||||||
|
void *ret = realloc(ptr, size);
|
||||||
|
|
||||||
|
if (!ret) {
|
||||||
|
fprintf(stderr, "Allocation failed.\n");
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_init(
|
||||||
|
hoedown_buffer *buf,
|
||||||
|
size_t unit,
|
||||||
|
hoedown_realloc_callback data_realloc,
|
||||||
|
hoedown_free_callback data_free,
|
||||||
|
hoedown_free_callback buffer_free)
|
||||||
|
{
|
||||||
|
assert(buf);
|
||||||
|
|
||||||
|
buf->data = NULL;
|
||||||
|
buf->size = buf->asize = 0;
|
||||||
|
buf->unit = unit;
|
||||||
|
buf->data_realloc = data_realloc;
|
||||||
|
buf->data_free = data_free;
|
||||||
|
buf->buffer_free = buffer_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_uninit(hoedown_buffer *buf)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
buf->data_free(buf->data);
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer *
|
||||||
|
hoedown_buffer_new(size_t unit)
|
||||||
|
{
|
||||||
|
hoedown_buffer *ret = hoedown_malloc(sizeof (hoedown_buffer));
|
||||||
|
hoedown_buffer_init(ret, unit, hoedown_realloc, free, free);
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_free(hoedown_buffer *buf)
|
||||||
|
{
|
||||||
|
if (!buf) return;
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
buf->data_free(buf->data);
|
||||||
|
|
||||||
|
if (buf->buffer_free)
|
||||||
|
buf->buffer_free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_reset(hoedown_buffer *buf)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
buf->data_free(buf->data);
|
||||||
|
buf->data = NULL;
|
||||||
|
buf->size = buf->asize = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_grow(hoedown_buffer *buf, size_t neosz)
|
||||||
|
{
|
||||||
|
size_t neoasz;
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (buf->asize >= neosz)
|
||||||
|
return;
|
||||||
|
|
||||||
|
neoasz = buf->asize + buf->unit;
|
||||||
|
while (neoasz < neosz)
|
||||||
|
neoasz += buf->unit;
|
||||||
|
|
||||||
|
buf->data = buf->data_realloc(buf->data, neoasz);
|
||||||
|
buf->asize = neoasz;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_put(hoedown_buffer *buf, const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (buf->size + size > buf->asize)
|
||||||
|
hoedown_buffer_grow(buf, buf->size + size);
|
||||||
|
|
||||||
|
memcpy(buf->data + buf->size, data, size);
|
||||||
|
buf->size += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_puts(hoedown_buffer *buf, const char *str)
|
||||||
|
{
|
||||||
|
hoedown_buffer_put(buf, (const uint8_t *)str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_putc(hoedown_buffer *buf, uint8_t c)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (buf->size >= buf->asize)
|
||||||
|
hoedown_buffer_grow(buf, buf->size + 1);
|
||||||
|
|
||||||
|
buf->data[buf->size] = c;
|
||||||
|
buf->size += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hoedown_buffer_putf(hoedown_buffer *buf, FILE *file)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
while (!(feof(file) || ferror(file))) {
|
||||||
|
hoedown_buffer_grow(buf, buf->size + buf->unit);
|
||||||
|
buf->size += fread(buf->data + buf->size, 1, buf->unit, file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ferror(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_set(hoedown_buffer *buf, const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (size > buf->asize)
|
||||||
|
hoedown_buffer_grow(buf, size);
|
||||||
|
|
||||||
|
memcpy(buf->data, data, size);
|
||||||
|
buf->size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_sets(hoedown_buffer *buf, const char *str)
|
||||||
|
{
|
||||||
|
hoedown_buffer_set(buf, (const uint8_t *)str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hoedown_buffer_eq(const hoedown_buffer *buf, const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
if (buf->size != size) return 0;
|
||||||
|
return memcmp(buf->data, data, size) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hoedown_buffer_eqs(const hoedown_buffer *buf, const char *str)
|
||||||
|
{
|
||||||
|
return hoedown_buffer_eq(buf, (const uint8_t *)str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
hoedown_buffer_prefix(const hoedown_buffer *buf, const char *prefix)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 0; i < buf->size; ++i) {
|
||||||
|
if (prefix[i] == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (buf->data[i] != prefix[i])
|
||||||
|
return buf->data[i] - prefix[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_slurp(hoedown_buffer *buf, size_t size)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (size >= buf->size) {
|
||||||
|
buf->size = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->size -= size;
|
||||||
|
memmove(buf->data, buf->data + size, buf->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *
|
||||||
|
hoedown_buffer_cstr(hoedown_buffer *buf)
|
||||||
|
{
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (buf->size < buf->asize && buf->data[buf->size] == 0)
|
||||||
|
return (char *)buf->data;
|
||||||
|
|
||||||
|
hoedown_buffer_grow(buf, buf->size + 1);
|
||||||
|
buf->data[buf->size] = 0;
|
||||||
|
|
||||||
|
return (char *)buf->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_buffer_printf(hoedown_buffer *buf, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
int n;
|
||||||
|
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (buf->size >= buf->asize)
|
||||||
|
hoedown_buffer_grow(buf, buf->size + 1);
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
|
||||||
|
if (n < 0) {
|
||||||
|
#ifndef _MSC_VER
|
||||||
|
return;
|
||||||
|
#else
|
||||||
|
va_start(ap, fmt);
|
||||||
|
n = _vscprintf(fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((size_t)n >= buf->asize - buf->size) {
|
||||||
|
hoedown_buffer_grow(buf, buf->size + n + 1);
|
||||||
|
|
||||||
|
va_start(ap, fmt);
|
||||||
|
n = vsnprintf((char *)buf->data + buf->size, buf->asize - buf->size, fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (n < 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
buf->size += n;
|
||||||
|
}
|
||||||
|
|
||||||
|
void hoedown_buffer_put_utf8(hoedown_buffer *buf, unsigned int c) {
|
||||||
|
unsigned char unichar[4];
|
||||||
|
|
||||||
|
assert(buf && buf->unit);
|
||||||
|
|
||||||
|
if (c < 0x80) {
|
||||||
|
hoedown_buffer_putc(buf, c);
|
||||||
|
}
|
||||||
|
else if (c < 0x800) {
|
||||||
|
unichar[0] = 192 + (c / 64);
|
||||||
|
unichar[1] = 128 + (c % 64);
|
||||||
|
hoedown_buffer_put(buf, unichar, 2);
|
||||||
|
}
|
||||||
|
else if (c - 0xd800u < 0x800) {
|
||||||
|
HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd");
|
||||||
|
}
|
||||||
|
else if (c < 0x10000) {
|
||||||
|
unichar[0] = 224 + (c / 4096);
|
||||||
|
unichar[1] = 128 + (c / 64) % 64;
|
||||||
|
unichar[2] = 128 + (c % 64);
|
||||||
|
hoedown_buffer_put(buf, unichar, 3);
|
||||||
|
}
|
||||||
|
else if (c < 0x110000) {
|
||||||
|
unichar[0] = 240 + (c / 262144);
|
||||||
|
unichar[1] = 128 + (c / 4096) % 64;
|
||||||
|
unichar[2] = 128 + (c / 64) % 64;
|
||||||
|
unichar[3] = 128 + (c % 64);
|
||||||
|
hoedown_buffer_put(buf, unichar, 4);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
HOEDOWN_BUFPUTSL(buf, "\xef\xbf\xbd");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,134 @@
|
||||||
|
/* buffer.h - simple, fast buffers */
|
||||||
|
|
||||||
|
#ifndef HOEDOWN_BUFFER_H
|
||||||
|
#define HOEDOWN_BUFFER_H
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define __attribute__(x)
|
||||||
|
#define inline __inline
|
||||||
|
#define __builtin_expect(x,n) x
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
* TYPES *
|
||||||
|
*********/
|
||||||
|
|
||||||
|
typedef void *(*hoedown_realloc_callback)(void *, size_t);
|
||||||
|
typedef void (*hoedown_free_callback)(void *);
|
||||||
|
|
||||||
|
struct hoedown_buffer {
|
||||||
|
uint8_t *data; /* actual character data */
|
||||||
|
size_t size; /* size of the string */
|
||||||
|
size_t asize; /* allocated size (0 = volatile buffer) */
|
||||||
|
size_t unit; /* reallocation unit size (0 = read-only buffer) */
|
||||||
|
|
||||||
|
hoedown_realloc_callback data_realloc;
|
||||||
|
hoedown_free_callback data_free;
|
||||||
|
hoedown_free_callback buffer_free;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct hoedown_buffer hoedown_buffer;
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* FUNCTIONS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
/* allocation wrappers */
|
||||||
|
void *hoedown_malloc(size_t size) __attribute__ ((malloc));
|
||||||
|
void *hoedown_calloc(size_t nmemb, size_t size) __attribute__ ((malloc));
|
||||||
|
void *hoedown_realloc(void *ptr, size_t size) __attribute__ ((malloc));
|
||||||
|
|
||||||
|
/* hoedown_buffer_init: initialize a buffer with custom allocators */
|
||||||
|
void hoedown_buffer_init(
|
||||||
|
hoedown_buffer *buffer,
|
||||||
|
size_t unit,
|
||||||
|
hoedown_realloc_callback data_realloc,
|
||||||
|
hoedown_free_callback data_free,
|
||||||
|
hoedown_free_callback buffer_free
|
||||||
|
);
|
||||||
|
|
||||||
|
/* hoedown_buffer_uninit: uninitialize an existing buffer */
|
||||||
|
void hoedown_buffer_uninit(hoedown_buffer *buf);
|
||||||
|
|
||||||
|
/* hoedown_buffer_new: allocate a new buffer */
|
||||||
|
hoedown_buffer *hoedown_buffer_new(size_t unit) __attribute__ ((malloc));
|
||||||
|
|
||||||
|
/* hoedown_buffer_reset: free internal data of the buffer */
|
||||||
|
void hoedown_buffer_reset(hoedown_buffer *buf);
|
||||||
|
|
||||||
|
/* hoedown_buffer_grow: increase the allocated size to the given value */
|
||||||
|
void hoedown_buffer_grow(hoedown_buffer *buf, size_t neosz);
|
||||||
|
|
||||||
|
/* hoedown_buffer_put: append raw data to a buffer */
|
||||||
|
void hoedown_buffer_put(hoedown_buffer *buf, const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_buffer_puts: append a NUL-terminated string to a buffer */
|
||||||
|
void hoedown_buffer_puts(hoedown_buffer *buf, const char *str);
|
||||||
|
|
||||||
|
/* hoedown_buffer_putc: append a single char to a buffer */
|
||||||
|
void hoedown_buffer_putc(hoedown_buffer *buf, uint8_t c);
|
||||||
|
|
||||||
|
/* hoedown_buffer_putf: read from a file and append to a buffer, until EOF or error */
|
||||||
|
int hoedown_buffer_putf(hoedown_buffer *buf, FILE* file);
|
||||||
|
|
||||||
|
/* hoedown_buffer_set: replace the buffer's contents with raw data */
|
||||||
|
void hoedown_buffer_set(hoedown_buffer *buf, const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_buffer_sets: replace the buffer's contents with a NUL-terminated string */
|
||||||
|
void hoedown_buffer_sets(hoedown_buffer *buf, const char *str);
|
||||||
|
|
||||||
|
/* hoedown_buffer_eq: compare a buffer's data with other data for equality */
|
||||||
|
int hoedown_buffer_eq(const hoedown_buffer *buf, const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_buffer_eq: compare a buffer's data with NUL-terminated string for equality */
|
||||||
|
int hoedown_buffer_eqs(const hoedown_buffer *buf, const char *str);
|
||||||
|
|
||||||
|
/* hoedown_buffer_prefix: compare the beginning of a buffer with a string */
|
||||||
|
int hoedown_buffer_prefix(const hoedown_buffer *buf, const char *prefix);
|
||||||
|
|
||||||
|
/* hoedown_buffer_slurp: remove a given number of bytes from the head of the buffer */
|
||||||
|
void hoedown_buffer_slurp(hoedown_buffer *buf, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_buffer_cstr: NUL-termination of the string array (making a C-string) */
|
||||||
|
const char *hoedown_buffer_cstr(hoedown_buffer *buf);
|
||||||
|
|
||||||
|
/* hoedown_buffer_printf: formatted printing to a buffer */
|
||||||
|
void hoedown_buffer_printf(hoedown_buffer *buf, const char *fmt, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
|
|
||||||
|
/* hoedown_buffer_put_utf8: put a Unicode character encoded as UTF-8 */
|
||||||
|
void hoedown_buffer_put_utf8(hoedown_buffer *buf, unsigned int codepoint);
|
||||||
|
|
||||||
|
/* hoedown_buffer_free: free the buffer */
|
||||||
|
void hoedown_buffer_free(hoedown_buffer *buf);
|
||||||
|
|
||||||
|
|
||||||
|
/* HOEDOWN_BUFPUTSL: optimized hoedown_buffer_puts of a string literal */
|
||||||
|
#define HOEDOWN_BUFPUTSL(output, literal) \
|
||||||
|
hoedown_buffer_put(output, (const uint8_t *)literal, sizeof(literal) - 1)
|
||||||
|
|
||||||
|
/* HOEDOWN_BUFSETSL: optimized hoedown_buffer_sets of a string literal */
|
||||||
|
#define HOEDOWN_BUFSETSL(output, literal) \
|
||||||
|
hoedown_buffer_set(output, (const uint8_t *)literal, sizeof(literal) - 1)
|
||||||
|
|
||||||
|
/* HOEDOWN_BUFEQSL: optimized hoedown_buffer_eqs of a string literal */
|
||||||
|
#define HOEDOWN_BUFEQSL(output, literal) \
|
||||||
|
hoedown_buffer_eq(output, (const uint8_t *)literal, sizeof(literal) - 1)
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /** HOEDOWN_BUFFER_H **/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,172 @@
|
||||||
|
/* document.h - generic markdown parser */
|
||||||
|
|
||||||
|
#ifndef HOEDOWN_DOCUMENT_H
|
||||||
|
#define HOEDOWN_DOCUMENT_H
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
#include "autolink.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* CONSTANTS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
typedef enum hoedown_extensions {
|
||||||
|
/* block-level extensions */
|
||||||
|
HOEDOWN_EXT_TABLES = (1 << 0),
|
||||||
|
HOEDOWN_EXT_FENCED_CODE = (1 << 1),
|
||||||
|
HOEDOWN_EXT_FOOTNOTES = (1 << 2),
|
||||||
|
|
||||||
|
/* span-level extensions */
|
||||||
|
HOEDOWN_EXT_AUTOLINK = (1 << 3),
|
||||||
|
HOEDOWN_EXT_STRIKETHROUGH = (1 << 4),
|
||||||
|
HOEDOWN_EXT_UNDERLINE = (1 << 5),
|
||||||
|
HOEDOWN_EXT_HIGHLIGHT = (1 << 6),
|
||||||
|
HOEDOWN_EXT_QUOTE = (1 << 7),
|
||||||
|
HOEDOWN_EXT_SUPERSCRIPT = (1 << 8),
|
||||||
|
HOEDOWN_EXT_MATH = (1 << 9),
|
||||||
|
|
||||||
|
/* other flags */
|
||||||
|
HOEDOWN_EXT_NO_INTRA_EMPHASIS = (1 << 11),
|
||||||
|
HOEDOWN_EXT_SPACE_HEADERS = (1 << 12),
|
||||||
|
HOEDOWN_EXT_MATH_EXPLICIT = (1 << 13),
|
||||||
|
|
||||||
|
/* negative flags */
|
||||||
|
HOEDOWN_EXT_DISABLE_INDENTED_CODE = (1 << 14)
|
||||||
|
} hoedown_extensions;
|
||||||
|
|
||||||
|
#define HOEDOWN_EXT_BLOCK (\
|
||||||
|
HOEDOWN_EXT_TABLES |\
|
||||||
|
HOEDOWN_EXT_FENCED_CODE |\
|
||||||
|
HOEDOWN_EXT_FOOTNOTES )
|
||||||
|
|
||||||
|
#define HOEDOWN_EXT_SPAN (\
|
||||||
|
HOEDOWN_EXT_AUTOLINK |\
|
||||||
|
HOEDOWN_EXT_STRIKETHROUGH |\
|
||||||
|
HOEDOWN_EXT_UNDERLINE |\
|
||||||
|
HOEDOWN_EXT_HIGHLIGHT |\
|
||||||
|
HOEDOWN_EXT_QUOTE |\
|
||||||
|
HOEDOWN_EXT_SUPERSCRIPT |\
|
||||||
|
HOEDOWN_EXT_MATH )
|
||||||
|
|
||||||
|
#define HOEDOWN_EXT_FLAGS (\
|
||||||
|
HOEDOWN_EXT_NO_INTRA_EMPHASIS |\
|
||||||
|
HOEDOWN_EXT_SPACE_HEADERS |\
|
||||||
|
HOEDOWN_EXT_MATH_EXPLICIT )
|
||||||
|
|
||||||
|
#define HOEDOWN_EXT_NEGATIVE (\
|
||||||
|
HOEDOWN_EXT_DISABLE_INDENTED_CODE )
|
||||||
|
|
||||||
|
typedef enum hoedown_list_flags {
|
||||||
|
HOEDOWN_LIST_ORDERED = (1 << 0),
|
||||||
|
HOEDOWN_LI_BLOCK = (1 << 1) /* <li> containing block data */
|
||||||
|
} hoedown_list_flags;
|
||||||
|
|
||||||
|
typedef enum hoedown_table_flags {
|
||||||
|
HOEDOWN_TABLE_ALIGN_LEFT = 1,
|
||||||
|
HOEDOWN_TABLE_ALIGN_RIGHT = 2,
|
||||||
|
HOEDOWN_TABLE_ALIGN_CENTER = 3,
|
||||||
|
HOEDOWN_TABLE_ALIGNMASK = 3,
|
||||||
|
HOEDOWN_TABLE_HEADER = 4
|
||||||
|
} hoedown_table_flags;
|
||||||
|
|
||||||
|
typedef enum hoedown_autolink_type {
|
||||||
|
HOEDOWN_AUTOLINK_NONE, /* used internally when it is not an autolink*/
|
||||||
|
HOEDOWN_AUTOLINK_NORMAL, /* normal http/http/ftp/mailto/etc link */
|
||||||
|
HOEDOWN_AUTOLINK_EMAIL /* e-mail link without explit mailto: */
|
||||||
|
} hoedown_autolink_type;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
* TYPES *
|
||||||
|
*********/
|
||||||
|
|
||||||
|
struct hoedown_document;
|
||||||
|
typedef struct hoedown_document hoedown_document;
|
||||||
|
|
||||||
|
struct hoedown_renderer_data {
|
||||||
|
void *opaque;
|
||||||
|
};
|
||||||
|
typedef struct hoedown_renderer_data hoedown_renderer_data;
|
||||||
|
|
||||||
|
/* hoedown_renderer - functions for rendering parsed data */
|
||||||
|
struct hoedown_renderer {
|
||||||
|
/* state object */
|
||||||
|
void *opaque;
|
||||||
|
|
||||||
|
/* block level callbacks - NULL skips the block */
|
||||||
|
void (*blockcode)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data);
|
||||||
|
void (*blockquote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
void (*header)(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data);
|
||||||
|
void (*hrule)(hoedown_buffer *ob, const hoedown_renderer_data *data);
|
||||||
|
void (*list)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data);
|
||||||
|
void (*listitem)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data);
|
||||||
|
void (*paragraph)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
void (*table)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
void (*table_header)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
void (*table_body)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
void (*table_row)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
void (*table_cell)(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data);
|
||||||
|
void (*footnotes)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
void (*footnote_def)(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data);
|
||||||
|
void (*blockhtml)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||||
|
|
||||||
|
/* span level callbacks - NULL or return 0 prints the span verbatim */
|
||||||
|
int (*autolink)(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data);
|
||||||
|
int (*codespan)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||||
|
int (*double_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*underline)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*highlight)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*quote)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*image)(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data);
|
||||||
|
int (*linebreak)(hoedown_buffer *ob, const hoedown_renderer_data *data);
|
||||||
|
int (*link)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data);
|
||||||
|
int (*triple_emphasis)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*strikethrough)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*superscript)(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data);
|
||||||
|
int (*footnote_ref)(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data);
|
||||||
|
int (*math)(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data);
|
||||||
|
int (*raw_html)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||||
|
|
||||||
|
/* low level callbacks - NULL copies input directly into the output */
|
||||||
|
void (*entity)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||||
|
void (*normal_text)(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data);
|
||||||
|
|
||||||
|
/* miscellaneous callbacks */
|
||||||
|
void (*doc_header)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data);
|
||||||
|
void (*doc_footer)(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data);
|
||||||
|
};
|
||||||
|
typedef struct hoedown_renderer hoedown_renderer;
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* FUNCTIONS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
/* hoedown_document_new: allocate a new document processor instance */
|
||||||
|
hoedown_document *hoedown_document_new(
|
||||||
|
const hoedown_renderer *renderer,
|
||||||
|
hoedown_extensions extensions,
|
||||||
|
size_t max_nesting
|
||||||
|
) __attribute__ ((malloc));
|
||||||
|
|
||||||
|
/* hoedown_document_render: render regular Markdown using the document processor */
|
||||||
|
void hoedown_document_render(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_document_render_inline: render inline Markdown using the document processor */
|
||||||
|
void hoedown_document_render_inline(hoedown_document *doc, hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_document_free: deallocate a document processor instance */
|
||||||
|
void hoedown_document_free(hoedown_document *doc);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /** HOEDOWN_DOCUMENT_H **/
|
|
@ -0,0 +1,188 @@
|
||||||
|
#include "escape.h"
|
||||||
|
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
|
||||||
|
#define likely(x) __builtin_expect((x),1)
|
||||||
|
#define unlikely(x) __builtin_expect((x),0)
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The following characters will not be escaped:
|
||||||
|
*
|
||||||
|
* -_.+!*'(),%#@?=;:/,+&$ alphanum
|
||||||
|
*
|
||||||
|
* Note that this character set is the addition of:
|
||||||
|
*
|
||||||
|
* - The characters which are safe to be in an URL
|
||||||
|
* - The characters which are *not* safe to be in
|
||||||
|
* an URL because they are RESERVED characters.
|
||||||
|
*
|
||||||
|
* We assume (lazily) that any RESERVED char that
|
||||||
|
* appears inside an URL is actually meant to
|
||||||
|
* have its native function (i.e. as an URL
|
||||||
|
* component/separator) and hence needs no escaping.
|
||||||
|
*
|
||||||
|
* There are two exceptions: the chacters & (amp)
|
||||||
|
* and ' (single quote) do not appear in the table.
|
||||||
|
* They are meant to appear in the URL as components,
|
||||||
|
* yet they require special HTML-entity escaping
|
||||||
|
* to generate valid HTML markup.
|
||||||
|
*
|
||||||
|
* All other characters will be escaped to %XX.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static const uint8_t HREF_SAFE[UINT8_MAX+1] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 1, 0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1,
|
||||||
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_escape_href(hoedown_buffer *ob, const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
static const char hex_chars[] = "0123456789ABCDEF";
|
||||||
|
size_t i = 0, mark;
|
||||||
|
char hex_str[3];
|
||||||
|
|
||||||
|
hex_str[0] = '%';
|
||||||
|
|
||||||
|
while (i < size) {
|
||||||
|
mark = i;
|
||||||
|
while (i < size && HREF_SAFE[data[i]]) i++;
|
||||||
|
|
||||||
|
/* Optimization for cases where there's nothing to escape */
|
||||||
|
if (mark == 0 && i >= size) {
|
||||||
|
hoedown_buffer_put(ob, data, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (likely(i > mark)) {
|
||||||
|
hoedown_buffer_put(ob, data + mark, i - mark);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* escaping */
|
||||||
|
if (i >= size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
switch (data[i]) {
|
||||||
|
/* amp appears all the time in URLs, but needs
|
||||||
|
* HTML-entity escaping to be inside an href */
|
||||||
|
case '&':
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "&");
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* the single quote is a valid URL character
|
||||||
|
* according to the standard; it needs HTML
|
||||||
|
* entity escaping too */
|
||||||
|
case '\'':
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "'");
|
||||||
|
break;
|
||||||
|
|
||||||
|
/* the space can be escaped to %20 or a plus
|
||||||
|
* sign. we're going with the generic escape
|
||||||
|
* for now. the plus thing is more commonly seen
|
||||||
|
* when building GET strings */
|
||||||
|
#if 0
|
||||||
|
case ' ':
|
||||||
|
hoedown_buffer_putc(ob, '+');
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* every other character goes with a %XX escaping */
|
||||||
|
default:
|
||||||
|
hex_str[1] = hex_chars[(data[i] >> 4) & 0xF];
|
||||||
|
hex_str[2] = hex_chars[data[i] & 0xF];
|
||||||
|
hoedown_buffer_put(ob, (uint8_t *)hex_str, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* According to the OWASP rules:
|
||||||
|
*
|
||||||
|
* & --> &
|
||||||
|
* < --> <
|
||||||
|
* > --> >
|
||||||
|
* " --> "
|
||||||
|
* ' --> ' ' is not recommended
|
||||||
|
* / --> / forward slash is included as it helps end an HTML entity
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
static const uint8_t HTML_ESCAPE_TABLE[UINT8_MAX+1] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 1, 0, 0, 0, 2, 3, 0, 0, 0, 0, 0, 0, 0, 4,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 6, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char *HTML_ESCAPES[] = {
|
||||||
|
"",
|
||||||
|
""",
|
||||||
|
"&",
|
||||||
|
"'",
|
||||||
|
"/",
|
||||||
|
"<",
|
||||||
|
">"
|
||||||
|
};
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_escape_html(hoedown_buffer *ob, const uint8_t *data, size_t size, int secure)
|
||||||
|
{
|
||||||
|
size_t i = 0, mark;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
mark = i;
|
||||||
|
while (i < size && HTML_ESCAPE_TABLE[data[i]] == 0) i++;
|
||||||
|
|
||||||
|
/* Optimization for cases where there's nothing to escape */
|
||||||
|
if (mark == 0 && i >= size) {
|
||||||
|
hoedown_buffer_put(ob, data, size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (likely(i > mark))
|
||||||
|
hoedown_buffer_put(ob, data + mark, i - mark);
|
||||||
|
|
||||||
|
if (i >= size) break;
|
||||||
|
|
||||||
|
/* The forward slash is only escaped in secure mode */
|
||||||
|
if (!secure && data[i] == '/') {
|
||||||
|
hoedown_buffer_putc(ob, '/');
|
||||||
|
} else {
|
||||||
|
hoedown_buffer_puts(ob, HTML_ESCAPES[HTML_ESCAPE_TABLE[data[i]]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/* escape.h - escape utilities */
|
||||||
|
|
||||||
|
#ifndef HOEDOWN_ESCAPE_H
|
||||||
|
#define HOEDOWN_ESCAPE_H
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* FUNCTIONS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
/* hoedown_escape_href: escape (part of) a URL inside HTML */
|
||||||
|
void hoedown_escape_href(hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_escape_html: escape HTML */
|
||||||
|
void hoedown_escape_html(hoedown_buffer *ob, const uint8_t *data, size_t size, int secure);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /** HOEDOWN_ESCAPE_H **/
|
|
@ -0,0 +1,754 @@
|
||||||
|
#include "html.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#include "escape.h"
|
||||||
|
|
||||||
|
#define USE_XHTML(opt) (opt->flags & HOEDOWN_HTML_USE_XHTML)
|
||||||
|
|
||||||
|
hoedown_html_tag
|
||||||
|
hoedown_html_is_tag(const uint8_t *data, size_t size, const char *tagname)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
int closed = 0;
|
||||||
|
|
||||||
|
if (size < 3 || data[0] != '<')
|
||||||
|
return HOEDOWN_HTML_TAG_NONE;
|
||||||
|
|
||||||
|
i = 1;
|
||||||
|
|
||||||
|
if (data[i] == '/') {
|
||||||
|
closed = 1;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; i < size; ++i, ++tagname) {
|
||||||
|
if (*tagname == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (data[i] != *tagname)
|
||||||
|
return HOEDOWN_HTML_TAG_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i == size)
|
||||||
|
return HOEDOWN_HTML_TAG_NONE;
|
||||||
|
|
||||||
|
if (isspace(data[i]) || data[i] == '>')
|
||||||
|
return closed ? HOEDOWN_HTML_TAG_CLOSE : HOEDOWN_HTML_TAG_OPEN;
|
||||||
|
|
||||||
|
return HOEDOWN_HTML_TAG_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void escape_html(hoedown_buffer *ob, const uint8_t *source, size_t length)
|
||||||
|
{
|
||||||
|
hoedown_escape_html(ob, source, length, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void escape_href(hoedown_buffer *ob, const uint8_t *source, size_t length)
|
||||||
|
{
|
||||||
|
hoedown_escape_href(ob, source, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/********************
|
||||||
|
* GENERIC RENDERER *
|
||||||
|
********************/
|
||||||
|
static int
|
||||||
|
rndr_autolink(hoedown_buffer *ob, const hoedown_buffer *link, hoedown_autolink_type type, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
|
||||||
|
if (!link || !link->size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<a href=\"");
|
||||||
|
if (type == HOEDOWN_AUTOLINK_EMAIL)
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "mailto:");
|
||||||
|
escape_href(ob, link->data, link->size);
|
||||||
|
|
||||||
|
if (state->link_attributes) {
|
||||||
|
hoedown_buffer_putc(ob, '\"');
|
||||||
|
state->link_attributes(ob, link, data);
|
||||||
|
hoedown_buffer_putc(ob, '>');
|
||||||
|
} else {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "\">");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pretty printing: if we get an email address as
|
||||||
|
* an actual URI, e.g. `mailto:foo@bar.com`, we don't
|
||||||
|
* want to print the `mailto:` prefix
|
||||||
|
*/
|
||||||
|
if (hoedown_buffer_prefix(link, "mailto:") == 0) {
|
||||||
|
escape_html(ob, link->data + 7, link->size - 7);
|
||||||
|
} else {
|
||||||
|
escape_html(ob, link->data, link->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</a>");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_blockcode(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_buffer *lang, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
|
||||||
|
if (lang) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<pre><code class=\"language-");
|
||||||
|
escape_html(ob, lang->data, lang->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "\">");
|
||||||
|
} else {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<pre><code>");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text)
|
||||||
|
escape_html(ob, text->data, text->size);
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</code></pre>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_blockquote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<blockquote>\n");
|
||||||
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</blockquote>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_codespan(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<code>");
|
||||||
|
if (text) escape_html(ob, text->data, text->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</code>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_strikethrough(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<del>");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</del>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_double_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<strong>");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</strong>");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size) return 0;
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<em>");
|
||||||
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</em>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_underline(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<u>");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</u>");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_highlight(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<mark>");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</mark>");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_quote(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<q>");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</q>");
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_linebreak(hoedown_buffer *ob, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
hoedown_buffer_puts(ob, USE_XHTML(state) ? "<br/>\n" : "<br>\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
|
||||||
|
if (ob->size)
|
||||||
|
hoedown_buffer_putc(ob, '\n');
|
||||||
|
|
||||||
|
if (level <= state->toc_data.nesting_level)
|
||||||
|
hoedown_buffer_printf(ob, "<h%d id=\"toc_%d\">", level, state->toc_data.header_count++);
|
||||||
|
else
|
||||||
|
hoedown_buffer_printf(ob, "<h%d>", level);
|
||||||
|
|
||||||
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
hoedown_buffer_printf(ob, "</h%d>\n", level);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<a href=\"");
|
||||||
|
|
||||||
|
if (link && link->size)
|
||||||
|
escape_href(ob, link->data, link->size);
|
||||||
|
|
||||||
|
if (title && title->size) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "\" title=\"");
|
||||||
|
escape_html(ob, title->data, title->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->link_attributes) {
|
||||||
|
hoedown_buffer_putc(ob, '\"');
|
||||||
|
state->link_attributes(ob, link, data);
|
||||||
|
hoedown_buffer_putc(ob, '>');
|
||||||
|
} else {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "\">");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content && content->size) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</a>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_list(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "<ol>\n" : "<ul>\n"), 5);
|
||||||
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
hoedown_buffer_put(ob, (const uint8_t *)(flags & HOEDOWN_LIST_ORDERED ? "</ol>\n" : "</ul>\n"), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_listitem(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_list_flags flags, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<li>");
|
||||||
|
if (content) {
|
||||||
|
size_t size = content->size;
|
||||||
|
while (size && content->data[size - 1] == '\n')
|
||||||
|
size--;
|
||||||
|
|
||||||
|
hoedown_buffer_put(ob, content->data, size);
|
||||||
|
}
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</li>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_paragraph(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
size_t i = 0;
|
||||||
|
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
|
||||||
|
if (!content || !content->size)
|
||||||
|
return;
|
||||||
|
|
||||||
|
while (i < content->size && isspace(content->data[i])) i++;
|
||||||
|
|
||||||
|
if (i == content->size)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<p>");
|
||||||
|
if (state->flags & HOEDOWN_HTML_HARD_WRAP) {
|
||||||
|
size_t org;
|
||||||
|
while (i < content->size) {
|
||||||
|
org = i;
|
||||||
|
while (i < content->size && content->data[i] != '\n')
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (i > org)
|
||||||
|
hoedown_buffer_put(ob, content->data + org, i - org);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* do not insert a line break if this newline
|
||||||
|
* is the last character on the paragraph
|
||||||
|
*/
|
||||||
|
if (i >= content->size - 1)
|
||||||
|
break;
|
||||||
|
|
||||||
|
rndr_linebreak(ob, data);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hoedown_buffer_put(ob, content->data + i, content->size - i);
|
||||||
|
}
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</p>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_raw_block(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
size_t org, sz;
|
||||||
|
|
||||||
|
if (!text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* FIXME: Do we *really* need to trim the HTML? How does that make a difference? */
|
||||||
|
sz = text->size;
|
||||||
|
while (sz > 0 && text->data[sz - 1] == '\n')
|
||||||
|
sz--;
|
||||||
|
|
||||||
|
org = 0;
|
||||||
|
while (org < sz && text->data[org] == '\n')
|
||||||
|
org++;
|
||||||
|
|
||||||
|
if (org >= sz)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ob->size)
|
||||||
|
hoedown_buffer_putc(ob, '\n');
|
||||||
|
|
||||||
|
hoedown_buffer_put(ob, text->data + org, sz - org);
|
||||||
|
hoedown_buffer_putc(ob, '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_triple_emphasis(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size) return 0;
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<strong><em>");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</em></strong>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_hrule(hoedown_buffer *ob, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
hoedown_buffer_puts(ob, USE_XHTML(state) ? "<hr/>\n" : "<hr>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_image(hoedown_buffer *ob, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_buffer *alt, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
if (!link || !link->size) return 0;
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<img src=\"");
|
||||||
|
escape_href(ob, link->data, link->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "\" alt=\"");
|
||||||
|
|
||||||
|
if (alt && alt->size)
|
||||||
|
escape_html(ob, alt->data, alt->size);
|
||||||
|
|
||||||
|
if (title && title->size) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "\" title=\"");
|
||||||
|
escape_html(ob, title->data, title->size); }
|
||||||
|
|
||||||
|
hoedown_buffer_puts(ob, USE_XHTML(state) ? "\"/>" : "\">");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_raw_html(hoedown_buffer *ob, const hoedown_buffer *text, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
|
||||||
|
/* ESCAPE overrides SKIP_HTML. It doesn't look to see if
|
||||||
|
* there are any valid tags, just escapes all of them. */
|
||||||
|
if((state->flags & HOEDOWN_HTML_ESCAPE) != 0) {
|
||||||
|
escape_html(ob, text->data, text->size);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((state->flags & HOEDOWN_HTML_SKIP_HTML) != 0)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
hoedown_buffer_put(ob, text->data, text->size);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_table(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<table>\n");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</table>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_table_header(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<thead>\n");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</thead>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_table_body(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<tbody>\n");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</tbody>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_tablerow(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<tr>\n");
|
||||||
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</tr>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_tablecell(hoedown_buffer *ob, const hoedown_buffer *content, hoedown_table_flags flags, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (flags & HOEDOWN_TABLE_HEADER) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<th");
|
||||||
|
} else {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<td");
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (flags & HOEDOWN_TABLE_ALIGNMASK) {
|
||||||
|
case HOEDOWN_TABLE_ALIGN_CENTER:
|
||||||
|
HOEDOWN_BUFPUTSL(ob, " style=\"text-align: center\">");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HOEDOWN_TABLE_ALIGN_LEFT:
|
||||||
|
HOEDOWN_BUFPUTSL(ob, " style=\"text-align: left\">");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HOEDOWN_TABLE_ALIGN_RIGHT:
|
||||||
|
HOEDOWN_BUFPUTSL(ob, " style=\"text-align: right\">");
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
HOEDOWN_BUFPUTSL(ob, ">");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content)
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
|
||||||
|
if (flags & HOEDOWN_TABLE_HEADER) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</th>\n");
|
||||||
|
} else {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</td>\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_superscript(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (!content || !content->size) return 0;
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<sup>");
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</sup>");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_normal_text(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (content)
|
||||||
|
escape_html(ob, content->data, content->size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_footnotes(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
|
||||||
|
if (ob->size) hoedown_buffer_putc(ob, '\n');
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<div class=\"footnotes\">\n");
|
||||||
|
hoedown_buffer_puts(ob, USE_XHTML(state) ? "<hr/>\n" : "<hr>\n");
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<ol>\n");
|
||||||
|
|
||||||
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "\n</ol>\n</div>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
rndr_footnote_def(hoedown_buffer *ob, const hoedown_buffer *content, unsigned int num, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
int pfound = 0;
|
||||||
|
|
||||||
|
/* insert anchor at the end of first paragraph block */
|
||||||
|
if (content) {
|
||||||
|
while ((i+3) < content->size) {
|
||||||
|
if (content->data[i++] != '<') continue;
|
||||||
|
if (content->data[i++] != '/') continue;
|
||||||
|
if (content->data[i++] != 'p' && content->data[i] != 'P') continue;
|
||||||
|
if (content->data[i] != '>') continue;
|
||||||
|
i -= 3;
|
||||||
|
pfound = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_printf(ob, "\n<li id=\"fn%d\">\n", num);
|
||||||
|
if (pfound) {
|
||||||
|
hoedown_buffer_put(ob, content->data, i);
|
||||||
|
hoedown_buffer_printf(ob, " <a href=\"#fnref%d\" rev=\"footnote\">↩</a>", num);
|
||||||
|
hoedown_buffer_put(ob, content->data + i, content->size - i);
|
||||||
|
} else if (content) {
|
||||||
|
hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
}
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</li>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_footnote_ref(hoedown_buffer *ob, unsigned int num, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_buffer_printf(ob, "<sup id=\"fnref%d\"><a href=\"#fn%d\" rel=\"footnote\">%d</a></sup>", num, num, num);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
rndr_math(hoedown_buffer *ob, const hoedown_buffer *text, int displaymode, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\[" : "\\("), 2);
|
||||||
|
escape_html(ob, text->data, text->size);
|
||||||
|
hoedown_buffer_put(ob, (const uint8_t *)(displaymode ? "\\]" : "\\)"), 2);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
toc_header(hoedown_buffer *ob, const hoedown_buffer *content, int level, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state = data->opaque;
|
||||||
|
|
||||||
|
if (level <= state->toc_data.nesting_level) {
|
||||||
|
/* set the level offset if this is the first header
|
||||||
|
* we're parsing for the document */
|
||||||
|
if (state->toc_data.current_level == 0)
|
||||||
|
state->toc_data.level_offset = level - 1;
|
||||||
|
|
||||||
|
level -= state->toc_data.level_offset;
|
||||||
|
|
||||||
|
if (level > state->toc_data.current_level) {
|
||||||
|
while (level > state->toc_data.current_level) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "<ul>\n<li>\n");
|
||||||
|
state->toc_data.current_level++;
|
||||||
|
}
|
||||||
|
} else if (level < state->toc_data.current_level) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</li>\n");
|
||||||
|
while (level < state->toc_data.current_level) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</ul>\n</li>\n");
|
||||||
|
state->toc_data.current_level--;
|
||||||
|
}
|
||||||
|
HOEDOWN_BUFPUTSL(ob,"<li>\n");
|
||||||
|
} else {
|
||||||
|
HOEDOWN_BUFPUTSL(ob,"</li>\n<li>\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_printf(ob, "<a href=\"#toc_%d\">", state->toc_data.header_count++);
|
||||||
|
if (content) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</a>\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
toc_link(hoedown_buffer *ob, const hoedown_buffer *content, const hoedown_buffer *link, const hoedown_buffer *title, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
if (content && content->size) hoedown_buffer_put(ob, content->data, content->size);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
toc_finalize(hoedown_buffer *ob, int inline_render, const hoedown_renderer_data *data)
|
||||||
|
{
|
||||||
|
hoedown_html_renderer_state *state;
|
||||||
|
|
||||||
|
if (inline_render)
|
||||||
|
return;
|
||||||
|
|
||||||
|
state = data->opaque;
|
||||||
|
|
||||||
|
while (state->toc_data.current_level > 0) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "</li>\n</ul>\n");
|
||||||
|
state->toc_data.current_level--;
|
||||||
|
}
|
||||||
|
|
||||||
|
state->toc_data.header_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_renderer *
|
||||||
|
hoedown_html_toc_renderer_new(int nesting_level)
|
||||||
|
{
|
||||||
|
static const hoedown_renderer cb_default = {
|
||||||
|
NULL,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
toc_header,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
rndr_codespan,
|
||||||
|
rndr_double_emphasis,
|
||||||
|
rndr_emphasis,
|
||||||
|
rndr_underline,
|
||||||
|
rndr_highlight,
|
||||||
|
rndr_quote,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
toc_link,
|
||||||
|
rndr_triple_emphasis,
|
||||||
|
rndr_strikethrough,
|
||||||
|
rndr_superscript,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
rndr_normal_text,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
toc_finalize
|
||||||
|
};
|
||||||
|
|
||||||
|
hoedown_html_renderer_state *state;
|
||||||
|
hoedown_renderer *renderer;
|
||||||
|
|
||||||
|
/* Prepare the state pointer */
|
||||||
|
state = hoedown_malloc(sizeof(hoedown_html_renderer_state));
|
||||||
|
memset(state, 0x0, sizeof(hoedown_html_renderer_state));
|
||||||
|
|
||||||
|
state->toc_data.nesting_level = nesting_level;
|
||||||
|
|
||||||
|
/* Prepare the renderer */
|
||||||
|
renderer = hoedown_malloc(sizeof(hoedown_renderer));
|
||||||
|
memcpy(renderer, &cb_default, sizeof(hoedown_renderer));
|
||||||
|
|
||||||
|
renderer->opaque = state;
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_renderer *
|
||||||
|
hoedown_html_renderer_new(hoedown_html_flags render_flags, int nesting_level)
|
||||||
|
{
|
||||||
|
static const hoedown_renderer cb_default = {
|
||||||
|
NULL,
|
||||||
|
|
||||||
|
rndr_blockcode,
|
||||||
|
rndr_blockquote,
|
||||||
|
rndr_header,
|
||||||
|
rndr_hrule,
|
||||||
|
rndr_list,
|
||||||
|
rndr_listitem,
|
||||||
|
rndr_paragraph,
|
||||||
|
rndr_table,
|
||||||
|
rndr_table_header,
|
||||||
|
rndr_table_body,
|
||||||
|
rndr_tablerow,
|
||||||
|
rndr_tablecell,
|
||||||
|
rndr_footnotes,
|
||||||
|
rndr_footnote_def,
|
||||||
|
rndr_raw_block,
|
||||||
|
|
||||||
|
rndr_autolink,
|
||||||
|
rndr_codespan,
|
||||||
|
rndr_double_emphasis,
|
||||||
|
rndr_emphasis,
|
||||||
|
rndr_underline,
|
||||||
|
rndr_highlight,
|
||||||
|
rndr_quote,
|
||||||
|
rndr_image,
|
||||||
|
rndr_linebreak,
|
||||||
|
rndr_link,
|
||||||
|
rndr_triple_emphasis,
|
||||||
|
rndr_strikethrough,
|
||||||
|
rndr_superscript,
|
||||||
|
rndr_footnote_ref,
|
||||||
|
rndr_math,
|
||||||
|
rndr_raw_html,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
rndr_normal_text,
|
||||||
|
|
||||||
|
NULL,
|
||||||
|
NULL
|
||||||
|
};
|
||||||
|
|
||||||
|
hoedown_html_renderer_state *state;
|
||||||
|
hoedown_renderer *renderer;
|
||||||
|
|
||||||
|
/* Prepare the state pointer */
|
||||||
|
state = hoedown_malloc(sizeof(hoedown_html_renderer_state));
|
||||||
|
memset(state, 0x0, sizeof(hoedown_html_renderer_state));
|
||||||
|
|
||||||
|
state->flags = render_flags;
|
||||||
|
state->toc_data.nesting_level = nesting_level;
|
||||||
|
|
||||||
|
/* Prepare the renderer */
|
||||||
|
renderer = hoedown_malloc(sizeof(hoedown_renderer));
|
||||||
|
memcpy(renderer, &cb_default, sizeof(hoedown_renderer));
|
||||||
|
|
||||||
|
if (render_flags & HOEDOWN_HTML_SKIP_HTML || render_flags & HOEDOWN_HTML_ESCAPE)
|
||||||
|
renderer->blockhtml = NULL;
|
||||||
|
|
||||||
|
renderer->opaque = state;
|
||||||
|
return renderer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_html_renderer_free(hoedown_renderer *renderer)
|
||||||
|
{
|
||||||
|
free(renderer->opaque);
|
||||||
|
free(renderer);
|
||||||
|
}
|
|
@ -0,0 +1,84 @@
|
||||||
|
/* html.h - HTML renderer and utilities */
|
||||||
|
|
||||||
|
#ifndef HOEDOWN_HTML_H
|
||||||
|
#define HOEDOWN_HTML_H
|
||||||
|
|
||||||
|
#include "document.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* CONSTANTS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
typedef enum hoedown_html_flags {
|
||||||
|
HOEDOWN_HTML_SKIP_HTML = (1 << 0),
|
||||||
|
HOEDOWN_HTML_ESCAPE = (1 << 1),
|
||||||
|
HOEDOWN_HTML_HARD_WRAP = (1 << 2),
|
||||||
|
HOEDOWN_HTML_USE_XHTML = (1 << 3)
|
||||||
|
} hoedown_html_flags;
|
||||||
|
|
||||||
|
typedef enum hoedown_html_tag {
|
||||||
|
HOEDOWN_HTML_TAG_NONE = 0,
|
||||||
|
HOEDOWN_HTML_TAG_OPEN,
|
||||||
|
HOEDOWN_HTML_TAG_CLOSE
|
||||||
|
} hoedown_html_tag;
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
* TYPES *
|
||||||
|
*********/
|
||||||
|
|
||||||
|
struct hoedown_html_renderer_state {
|
||||||
|
void *opaque;
|
||||||
|
|
||||||
|
struct {
|
||||||
|
int header_count;
|
||||||
|
int current_level;
|
||||||
|
int level_offset;
|
||||||
|
int nesting_level;
|
||||||
|
} toc_data;
|
||||||
|
|
||||||
|
hoedown_html_flags flags;
|
||||||
|
|
||||||
|
/* extra callbacks */
|
||||||
|
void (*link_attributes)(hoedown_buffer *ob, const hoedown_buffer *url, const hoedown_renderer_data *data);
|
||||||
|
};
|
||||||
|
typedef struct hoedown_html_renderer_state hoedown_html_renderer_state;
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* FUNCTIONS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
/* hoedown_html_smartypants: process an HTML snippet using SmartyPants for smart punctuation */
|
||||||
|
void hoedown_html_smartypants(hoedown_buffer *ob, const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
/* hoedown_html_is_tag: checks if data starts with a specific tag, returns the tag type or NONE */
|
||||||
|
hoedown_html_tag hoedown_html_is_tag(const uint8_t *data, size_t size, const char *tagname);
|
||||||
|
|
||||||
|
|
||||||
|
/* hoedown_html_renderer_new: allocates a regular HTML renderer */
|
||||||
|
hoedown_renderer *hoedown_html_renderer_new(
|
||||||
|
hoedown_html_flags render_flags,
|
||||||
|
int nesting_level
|
||||||
|
) __attribute__ ((malloc));
|
||||||
|
|
||||||
|
/* hoedown_html_toc_renderer_new: like hoedown_html_renderer_new, but the returned renderer produces the Table of Contents */
|
||||||
|
hoedown_renderer *hoedown_html_toc_renderer_new(
|
||||||
|
int nesting_level
|
||||||
|
) __attribute__ ((malloc));
|
||||||
|
|
||||||
|
/* hoedown_html_renderer_free: deallocate an HTML renderer */
|
||||||
|
void hoedown_html_renderer_free(hoedown_renderer *renderer);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /** HOEDOWN_HTML_H **/
|
|
@ -0,0 +1,240 @@
|
||||||
|
/* ANSI-C code produced by gperf version 3.0.3 */
|
||||||
|
/* Command-line: gperf -L ANSI-C -N hoedown_find_block_tag -c -C -E -S 1 --ignore-case -m100 html_block_names.gperf */
|
||||||
|
/* Computed positions: -k'1-2' */
|
||||||
|
|
||||||
|
#if !((' ' == 32) && ('!' == 33) && ('"' == 34) && ('#' == 35) \
|
||||||
|
&& ('%' == 37) && ('&' == 38) && ('\'' == 39) && ('(' == 40) \
|
||||||
|
&& (')' == 41) && ('*' == 42) && ('+' == 43) && (',' == 44) \
|
||||||
|
&& ('-' == 45) && ('.' == 46) && ('/' == 47) && ('0' == 48) \
|
||||||
|
&& ('1' == 49) && ('2' == 50) && ('3' == 51) && ('4' == 52) \
|
||||||
|
&& ('5' == 53) && ('6' == 54) && ('7' == 55) && ('8' == 56) \
|
||||||
|
&& ('9' == 57) && (':' == 58) && (';' == 59) && ('<' == 60) \
|
||||||
|
&& ('=' == 61) && ('>' == 62) && ('?' == 63) && ('A' == 65) \
|
||||||
|
&& ('B' == 66) && ('C' == 67) && ('D' == 68) && ('E' == 69) \
|
||||||
|
&& ('F' == 70) && ('G' == 71) && ('H' == 72) && ('I' == 73) \
|
||||||
|
&& ('J' == 74) && ('K' == 75) && ('L' == 76) && ('M' == 77) \
|
||||||
|
&& ('N' == 78) && ('O' == 79) && ('P' == 80) && ('Q' == 81) \
|
||||||
|
&& ('R' == 82) && ('S' == 83) && ('T' == 84) && ('U' == 85) \
|
||||||
|
&& ('V' == 86) && ('W' == 87) && ('X' == 88) && ('Y' == 89) \
|
||||||
|
&& ('Z' == 90) && ('[' == 91) && ('\\' == 92) && (']' == 93) \
|
||||||
|
&& ('^' == 94) && ('_' == 95) && ('a' == 97) && ('b' == 98) \
|
||||||
|
&& ('c' == 99) && ('d' == 100) && ('e' == 101) && ('f' == 102) \
|
||||||
|
&& ('g' == 103) && ('h' == 104) && ('i' == 105) && ('j' == 106) \
|
||||||
|
&& ('k' == 107) && ('l' == 108) && ('m' == 109) && ('n' == 110) \
|
||||||
|
&& ('o' == 111) && ('p' == 112) && ('q' == 113) && ('r' == 114) \
|
||||||
|
&& ('s' == 115) && ('t' == 116) && ('u' == 117) && ('v' == 118) \
|
||||||
|
&& ('w' == 119) && ('x' == 120) && ('y' == 121) && ('z' == 122) \
|
||||||
|
&& ('{' == 123) && ('|' == 124) && ('}' == 125) && ('~' == 126))
|
||||||
|
/* The character set is not based on ISO-646. */
|
||||||
|
#error "gperf generated tables don't work with this execution character set. Please report a bug to <bug-gnu-gperf@gnu.org>."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* maximum key range = 24, duplicates = 0 */
|
||||||
|
|
||||||
|
#ifndef GPERF_DOWNCASE
|
||||||
|
#define GPERF_DOWNCASE 1
|
||||||
|
static unsigned char gperf_downcase[256] =
|
||||||
|
{
|
||||||
|
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||||
|
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
|
||||||
|
30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44,
|
||||||
|
45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
|
||||||
|
60, 61, 62, 63, 64, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106,
|
||||||
|
107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121,
|
||||||
|
122, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104,
|
||||||
|
105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
|
||||||
|
120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134,
|
||||||
|
135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, 147, 148, 149,
|
||||||
|
150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164,
|
||||||
|
165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179,
|
||||||
|
180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194,
|
||||||
|
195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 208, 209,
|
||||||
|
210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 224,
|
||||||
|
225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239,
|
||||||
|
240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254,
|
||||||
|
255
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef GPERF_CASE_STRNCMP
|
||||||
|
#define GPERF_CASE_STRNCMP 1
|
||||||
|
static int
|
||||||
|
gperf_case_strncmp (register const char *s1, register const char *s2, register unsigned int n)
|
||||||
|
{
|
||||||
|
for (; n > 0;)
|
||||||
|
{
|
||||||
|
unsigned char c1 = gperf_downcase[(unsigned char)*s1++];
|
||||||
|
unsigned char c2 = gperf_downcase[(unsigned char)*s2++];
|
||||||
|
if (c1 != 0 && c1 == c2)
|
||||||
|
{
|
||||||
|
n--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return (int)c1 - (int)c2;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
__inline
|
||||||
|
#else
|
||||||
|
#ifdef __cplusplus
|
||||||
|
inline
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
static unsigned int
|
||||||
|
hash (register const char *str, register unsigned int len)
|
||||||
|
{
|
||||||
|
static const unsigned char asso_values[] =
|
||||||
|
{
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
22, 21, 19, 18, 16, 0, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 1, 25, 0, 25,
|
||||||
|
1, 0, 0, 13, 0, 25, 25, 11, 2, 1,
|
||||||
|
0, 25, 25, 5, 0, 2, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 1, 25,
|
||||||
|
0, 25, 1, 0, 0, 13, 0, 25, 25, 11,
|
||||||
|
2, 1, 0, 25, 25, 5, 0, 2, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25, 25, 25, 25,
|
||||||
|
25, 25, 25, 25, 25, 25, 25
|
||||||
|
};
|
||||||
|
register int hval = (int)len;
|
||||||
|
|
||||||
|
switch (hval)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
hval += asso_values[(unsigned char)str[1]+1];
|
||||||
|
/*FALLTHROUGH*/
|
||||||
|
case 1:
|
||||||
|
hval += asso_values[(unsigned char)str[0]];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return hval;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __GNUC__
|
||||||
|
__inline
|
||||||
|
#ifdef __GNUC_STDC_INLINE__
|
||||||
|
__attribute__ ((__gnu_inline__))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
const char *
|
||||||
|
hoedown_find_block_tag (register const char *str, register unsigned int len)
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
TOTAL_KEYWORDS = 24,
|
||||||
|
MIN_WORD_LENGTH = 1,
|
||||||
|
MAX_WORD_LENGTH = 10,
|
||||||
|
MIN_HASH_VALUE = 1,
|
||||||
|
MAX_HASH_VALUE = 24
|
||||||
|
};
|
||||||
|
|
||||||
|
if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH)
|
||||||
|
{
|
||||||
|
register int key = hash (str, len);
|
||||||
|
|
||||||
|
if (key <= MAX_HASH_VALUE && key >= MIN_HASH_VALUE)
|
||||||
|
{
|
||||||
|
register const char *resword;
|
||||||
|
|
||||||
|
switch (key - 1)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
resword = "p";
|
||||||
|
goto compare;
|
||||||
|
case 1:
|
||||||
|
resword = "h6";
|
||||||
|
goto compare;
|
||||||
|
case 2:
|
||||||
|
resword = "div";
|
||||||
|
goto compare;
|
||||||
|
case 3:
|
||||||
|
resword = "del";
|
||||||
|
goto compare;
|
||||||
|
case 4:
|
||||||
|
resword = "form";
|
||||||
|
goto compare;
|
||||||
|
case 5:
|
||||||
|
resword = "table";
|
||||||
|
goto compare;
|
||||||
|
case 6:
|
||||||
|
resword = "figure";
|
||||||
|
goto compare;
|
||||||
|
case 7:
|
||||||
|
resword = "pre";
|
||||||
|
goto compare;
|
||||||
|
case 8:
|
||||||
|
resword = "fieldset";
|
||||||
|
goto compare;
|
||||||
|
case 9:
|
||||||
|
resword = "noscript";
|
||||||
|
goto compare;
|
||||||
|
case 10:
|
||||||
|
resword = "script";
|
||||||
|
goto compare;
|
||||||
|
case 11:
|
||||||
|
resword = "style";
|
||||||
|
goto compare;
|
||||||
|
case 12:
|
||||||
|
resword = "dl";
|
||||||
|
goto compare;
|
||||||
|
case 13:
|
||||||
|
resword = "ol";
|
||||||
|
goto compare;
|
||||||
|
case 14:
|
||||||
|
resword = "ul";
|
||||||
|
goto compare;
|
||||||
|
case 15:
|
||||||
|
resword = "math";
|
||||||
|
goto compare;
|
||||||
|
case 16:
|
||||||
|
resword = "ins";
|
||||||
|
goto compare;
|
||||||
|
case 17:
|
||||||
|
resword = "h5";
|
||||||
|
goto compare;
|
||||||
|
case 18:
|
||||||
|
resword = "iframe";
|
||||||
|
goto compare;
|
||||||
|
case 19:
|
||||||
|
resword = "h4";
|
||||||
|
goto compare;
|
||||||
|
case 20:
|
||||||
|
resword = "h3";
|
||||||
|
goto compare;
|
||||||
|
case 21:
|
||||||
|
resword = "blockquote";
|
||||||
|
goto compare;
|
||||||
|
case 22:
|
||||||
|
resword = "h2";
|
||||||
|
goto compare;
|
||||||
|
case 23:
|
||||||
|
resword = "h1";
|
||||||
|
goto compare;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
compare:
|
||||||
|
if ((((unsigned char)*str ^ (unsigned char)*resword) & ~32) == 0 && !gperf_case_strncmp (str, resword, len) && resword[len] == '\0')
|
||||||
|
return resword;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,435 @@
|
||||||
|
#include "html.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define snprintf _snprintf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
struct smartypants_data {
|
||||||
|
int in_squote;
|
||||||
|
int in_dquote;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t smartypants_cb__ltag(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__dquote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__amp(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__period(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__number(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__dash(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__parens(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__backtick(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
static size_t smartypants_cb__escape(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size);
|
||||||
|
|
||||||
|
static size_t (*smartypants_cb_ptrs[])
|
||||||
|
(hoedown_buffer *, struct smartypants_data *, uint8_t, const uint8_t *, size_t) =
|
||||||
|
{
|
||||||
|
NULL, /* 0 */
|
||||||
|
smartypants_cb__dash, /* 1 */
|
||||||
|
smartypants_cb__parens, /* 2 */
|
||||||
|
smartypants_cb__squote, /* 3 */
|
||||||
|
smartypants_cb__dquote, /* 4 */
|
||||||
|
smartypants_cb__amp, /* 5 */
|
||||||
|
smartypants_cb__period, /* 6 */
|
||||||
|
smartypants_cb__number, /* 7 */
|
||||||
|
smartypants_cb__ltag, /* 8 */
|
||||||
|
smartypants_cb__backtick, /* 9 */
|
||||||
|
smartypants_cb__escape, /* 10 */
|
||||||
|
};
|
||||||
|
|
||||||
|
static const uint8_t smartypants_cb_chars[UINT8_MAX+1] = {
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 4, 0, 0, 0, 5, 3, 2, 0, 0, 0, 0, 1, 6, 0,
|
||||||
|
0, 7, 0, 7, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0,
|
||||||
|
9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
word_boundary(uint8_t c)
|
||||||
|
{
|
||||||
|
return c == 0 || isspace(c) || ispunct(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
If 'text' begins with any kind of single quote (e.g. "'" or "'" etc.),
|
||||||
|
returns the length of the sequence of characters that makes up the single-
|
||||||
|
quote. Otherwise, returns zero.
|
||||||
|
*/
|
||||||
|
static size_t
|
||||||
|
squote_len(const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
static char* single_quote_list[] = { "'", "'", "'", "'", NULL };
|
||||||
|
char** p;
|
||||||
|
|
||||||
|
for (p = single_quote_list; *p; ++p) {
|
||||||
|
size_t len = strlen(*p);
|
||||||
|
if (size >= len && memcmp(text, *p, len) == 0) {
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts " or ' at very beginning or end of a word to left or right quote */
|
||||||
|
static int
|
||||||
|
smartypants_quotes(hoedown_buffer *ob, uint8_t previous_char, uint8_t next_char, uint8_t quote, int *is_open)
|
||||||
|
{
|
||||||
|
char ent[8];
|
||||||
|
|
||||||
|
if (*is_open && !word_boundary(next_char))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if (!(*is_open) && !word_boundary(previous_char))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
snprintf(ent, sizeof(ent), "&%c%cquo;", (*is_open) ? 'r' : 'l', quote);
|
||||||
|
*is_open = !(*is_open);
|
||||||
|
hoedown_buffer_puts(ob, ent);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
Converts ' to left or right single quote; but the initial ' might be in
|
||||||
|
different forms, e.g. ' or ' or '.
|
||||||
|
'squote_text' points to the original single quote, and 'squote_size' is its length.
|
||||||
|
'text' points at the last character of the single-quote, e.g. ' or ;
|
||||||
|
*/
|
||||||
|
static size_t
|
||||||
|
smartypants_squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size,
|
||||||
|
const uint8_t *squote_text, size_t squote_size)
|
||||||
|
{
|
||||||
|
if (size >= 2) {
|
||||||
|
uint8_t t1 = tolower(text[1]);
|
||||||
|
size_t next_squote_len = squote_len(text+1, size-1);
|
||||||
|
|
||||||
|
/* convert '' to “ or ” */
|
||||||
|
if (next_squote_len > 0) {
|
||||||
|
uint8_t next_char = (size > 1+next_squote_len) ? text[1+next_squote_len] : 0;
|
||||||
|
if (smartypants_quotes(ob, previous_char, next_char, 'd', &smrt->in_dquote))
|
||||||
|
return next_squote_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tom's, isn't, I'm, I'd */
|
||||||
|
if ((t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') &&
|
||||||
|
(size == 3 || word_boundary(text[2]))) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "’");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* you're, you'll, you've */
|
||||||
|
if (size >= 3) {
|
||||||
|
uint8_t t2 = tolower(text[2]);
|
||||||
|
|
||||||
|
if (((t1 == 'r' && t2 == 'e') ||
|
||||||
|
(t1 == 'l' && t2 == 'l') ||
|
||||||
|
(t1 == 'v' && t2 == 'e')) &&
|
||||||
|
(size == 4 || word_boundary(text[3]))) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "’");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 's', &smrt->in_squote))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
hoedown_buffer_put(ob, squote_text, squote_size);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts ' to left or right single quote. */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__squote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
return smartypants_squote(ob, smrt, previous_char, text, size, text, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts (c), (r), (tm) */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__parens(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
if (size >= 3) {
|
||||||
|
uint8_t t1 = tolower(text[1]);
|
||||||
|
uint8_t t2 = tolower(text[2]);
|
||||||
|
|
||||||
|
if (t1 == 'c' && t2 == ')') {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "©");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t1 == 'r' && t2 == ')') {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "®");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')') {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "™");
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_putc(ob, text[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts "--" to em-dash, etc. */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__dash(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
if (size >= 3 && text[1] == '-' && text[2] == '-') {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "—");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size >= 2 && text[1] == '-') {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "–");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_putc(ob, text[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts " etc. */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__amp(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
size_t len;
|
||||||
|
if (size >= 6 && memcmp(text, """, 6) == 0) {
|
||||||
|
if (smartypants_quotes(ob, previous_char, size >= 7 ? text[6] : 0, 'd', &smrt->in_dquote))
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
len = squote_len(text, size);
|
||||||
|
if (len > 0) {
|
||||||
|
return (len-1) + smartypants_squote(ob, smrt, previous_char, text+(len-1), size-(len-1), text, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size >= 4 && memcmp(text, "�", 4) == 0)
|
||||||
|
return 3;
|
||||||
|
|
||||||
|
hoedown_buffer_putc(ob, '&');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts "..." to ellipsis */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__period(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
if (size >= 3 && text[1] == '.' && text[2] == '.') {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "…");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.') {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "…");
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_putc(ob, text[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts `` to opening double quote */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__backtick(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
if (size >= 2 && text[1] == '`') {
|
||||||
|
if (smartypants_quotes(ob, previous_char, size >= 3 ? text[2] : 0, 'd', &smrt->in_dquote))
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_putc(ob, text[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts 1/2, 1/4, 3/4 */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__number(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
if (word_boundary(previous_char) && size >= 3) {
|
||||||
|
if (text[0] == '1' && text[1] == '/' && text[2] == '2') {
|
||||||
|
if (size == 3 || word_boundary(text[3])) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "½");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text[0] == '1' && text[1] == '/' && text[2] == '4') {
|
||||||
|
if (size == 3 || word_boundary(text[3]) ||
|
||||||
|
(size >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h')) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "¼");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (text[0] == '3' && text[1] == '/' && text[2] == '4') {
|
||||||
|
if (size == 3 || word_boundary(text[3]) ||
|
||||||
|
(size >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's')) {
|
||||||
|
HOEDOWN_BUFPUTSL(ob, "¾");
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_putc(ob, text[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Converts " to left or right double quote */
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__dquote(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
if (!smartypants_quotes(ob, previous_char, size > 0 ? text[1] : 0, 'd', &smrt->in_dquote))
|
||||||
|
HOEDOWN_BUFPUTSL(ob, """);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__ltag(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
static const char *skip_tags[] = {
|
||||||
|
"pre", "code", "var", "samp", "kbd", "math", "script", "style"
|
||||||
|
};
|
||||||
|
static const size_t skip_tags_count = 8;
|
||||||
|
|
||||||
|
size_t tag, i = 0;
|
||||||
|
|
||||||
|
/* This is a comment. Copy everything verbatim until --> or EOF is seen. */
|
||||||
|
if (i + 4 < size && memcmp(text + i, "<!--", 4) == 0) {
|
||||||
|
i += 4;
|
||||||
|
while (i + 3 < size && memcmp(text + i, "-->", 3) != 0)
|
||||||
|
i++;
|
||||||
|
i += 3;
|
||||||
|
hoedown_buffer_put(ob, text, i + 1);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < size && text[i] != '>')
|
||||||
|
i++;
|
||||||
|
|
||||||
|
for (tag = 0; tag < skip_tags_count; ++tag) {
|
||||||
|
if (hoedown_html_is_tag(text, size, skip_tags[tag]) == HOEDOWN_HTML_TAG_OPEN)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tag < skip_tags_count) {
|
||||||
|
for (;;) {
|
||||||
|
while (i < size && text[i] != '<')
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (i == size)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (hoedown_html_is_tag(text + i, size - i, skip_tags[tag]) == HOEDOWN_HTML_TAG_CLOSE)
|
||||||
|
break;
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (i < size && text[i] != '>')
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoedown_buffer_put(ob, text, i + 1);
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
smartypants_cb__escape(hoedown_buffer *ob, struct smartypants_data *smrt, uint8_t previous_char, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
if (size < 2)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
switch (text[1]) {
|
||||||
|
case '\\':
|
||||||
|
case '"':
|
||||||
|
case '\'':
|
||||||
|
case '.':
|
||||||
|
case '-':
|
||||||
|
case '`':
|
||||||
|
hoedown_buffer_putc(ob, text[1]);
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
default:
|
||||||
|
hoedown_buffer_putc(ob, '\\');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
static struct {
|
||||||
|
uint8_t c0;
|
||||||
|
const uint8_t *pattern;
|
||||||
|
const uint8_t *entity;
|
||||||
|
int skip;
|
||||||
|
} smartypants_subs[] = {
|
||||||
|
{ '\'', "'s>", "’", 0 },
|
||||||
|
{ '\'', "'t>", "’", 0 },
|
||||||
|
{ '\'', "'re>", "’", 0 },
|
||||||
|
{ '\'', "'ll>", "’", 0 },
|
||||||
|
{ '\'', "'ve>", "’", 0 },
|
||||||
|
{ '\'', "'m>", "’", 0 },
|
||||||
|
{ '\'', "'d>", "’", 0 },
|
||||||
|
{ '-', "--", "—", 1 },
|
||||||
|
{ '-', "<->", "–", 0 },
|
||||||
|
{ '.', "...", "…", 2 },
|
||||||
|
{ '.', ". . .", "…", 4 },
|
||||||
|
{ '(', "(c)", "©", 2 },
|
||||||
|
{ '(', "(r)", "®", 2 },
|
||||||
|
{ '(', "(tm)", "™", 3 },
|
||||||
|
{ '3', "<3/4>", "¾", 2 },
|
||||||
|
{ '3', "<3/4ths>", "¾", 2 },
|
||||||
|
{ '1', "<1/2>", "½", 2 },
|
||||||
|
{ '1', "<1/4>", "¼", 2 },
|
||||||
|
{ '1', "<1/4th>", "¼", 2 },
|
||||||
|
{ '&', "�", 0, 3 },
|
||||||
|
};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_html_smartypants(hoedown_buffer *ob, const uint8_t *text, size_t size)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
struct smartypants_data smrt = {0, 0};
|
||||||
|
|
||||||
|
if (!text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
hoedown_buffer_grow(ob, size);
|
||||||
|
|
||||||
|
for (i = 0; i < size; ++i) {
|
||||||
|
size_t org;
|
||||||
|
uint8_t action = 0;
|
||||||
|
|
||||||
|
org = i;
|
||||||
|
while (i < size && (action = smartypants_cb_chars[text[i]]) == 0)
|
||||||
|
i++;
|
||||||
|
|
||||||
|
if (i > org)
|
||||||
|
hoedown_buffer_put(ob, text + org, i - org);
|
||||||
|
|
||||||
|
if (i < size) {
|
||||||
|
i += smartypants_cb_ptrs[(int)action]
|
||||||
|
(ob, &smrt, i ? text[i - 1] : 0, text + i, size - i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
#include "stack.h"
|
||||||
|
|
||||||
|
#include "buffer.h"
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_stack_init(hoedown_stack *st, size_t initial_size)
|
||||||
|
{
|
||||||
|
assert(st);
|
||||||
|
|
||||||
|
st->item = NULL;
|
||||||
|
st->size = st->asize = 0;
|
||||||
|
|
||||||
|
if (!initial_size)
|
||||||
|
initial_size = 8;
|
||||||
|
|
||||||
|
hoedown_stack_grow(st, initial_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_stack_uninit(hoedown_stack *st)
|
||||||
|
{
|
||||||
|
assert(st);
|
||||||
|
|
||||||
|
free(st->item);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_stack_grow(hoedown_stack *st, size_t neosz)
|
||||||
|
{
|
||||||
|
assert(st);
|
||||||
|
|
||||||
|
if (st->asize >= neosz)
|
||||||
|
return;
|
||||||
|
|
||||||
|
st->item = hoedown_realloc(st->item, neosz * sizeof(void *));
|
||||||
|
memset(st->item + st->asize, 0x0, (neosz - st->asize) * sizeof(void *));
|
||||||
|
|
||||||
|
st->asize = neosz;
|
||||||
|
|
||||||
|
if (st->size > neosz)
|
||||||
|
st->size = neosz;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_stack_push(hoedown_stack *st, void *item)
|
||||||
|
{
|
||||||
|
assert(st);
|
||||||
|
|
||||||
|
if (st->size >= st->asize)
|
||||||
|
hoedown_stack_grow(st, st->size * 2);
|
||||||
|
|
||||||
|
st->item[st->size++] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
hoedown_stack_pop(hoedown_stack *st)
|
||||||
|
{
|
||||||
|
assert(st);
|
||||||
|
|
||||||
|
if (!st->size)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return st->item[--st->size];
|
||||||
|
}
|
||||||
|
|
||||||
|
void *
|
||||||
|
hoedown_stack_top(const hoedown_stack *st)
|
||||||
|
{
|
||||||
|
assert(st);
|
||||||
|
|
||||||
|
if (!st->size)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
return st->item[st->size - 1];
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/* stack.h - simple stacking */
|
||||||
|
|
||||||
|
#ifndef HOEDOWN_STACK_H
|
||||||
|
#define HOEDOWN_STACK_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*********
|
||||||
|
* TYPES *
|
||||||
|
*********/
|
||||||
|
|
||||||
|
struct hoedown_stack {
|
||||||
|
void **item;
|
||||||
|
size_t size;
|
||||||
|
size_t asize;
|
||||||
|
};
|
||||||
|
typedef struct hoedown_stack hoedown_stack;
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* FUNCTIONS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
/* hoedown_stack_init: initialize a stack */
|
||||||
|
void hoedown_stack_init(hoedown_stack *st, size_t initial_size);
|
||||||
|
|
||||||
|
/* hoedown_stack_uninit: free internal data of the stack */
|
||||||
|
void hoedown_stack_uninit(hoedown_stack *st);
|
||||||
|
|
||||||
|
/* hoedown_stack_grow: increase the allocated size to the given value */
|
||||||
|
void hoedown_stack_grow(hoedown_stack *st, size_t neosz);
|
||||||
|
|
||||||
|
/* hoedown_stack_push: push an item to the top of the stack */
|
||||||
|
void hoedown_stack_push(hoedown_stack *st, void *item);
|
||||||
|
|
||||||
|
/* hoedown_stack_pop: retrieve and remove the item at the top of the stack */
|
||||||
|
void *hoedown_stack_pop(hoedown_stack *st);
|
||||||
|
|
||||||
|
/* hoedown_stack_top: retrieve the item at the top of the stack */
|
||||||
|
void *hoedown_stack_top(const hoedown_stack *st);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /** HOEDOWN_STACK_H **/
|
|
@ -0,0 +1,9 @@
|
||||||
|
#include "version.h"
|
||||||
|
|
||||||
|
void
|
||||||
|
hoedown_version(int *major, int *minor, int *revision)
|
||||||
|
{
|
||||||
|
*major = HOEDOWN_VERSION_MAJOR;
|
||||||
|
*minor = HOEDOWN_VERSION_MINOR;
|
||||||
|
*revision = HOEDOWN_VERSION_REVISION;
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* version.h - holds Hoedown's version */
|
||||||
|
|
||||||
|
#ifndef HOEDOWN_VERSION_H
|
||||||
|
#define HOEDOWN_VERSION_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* CONSTANTS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
#define HOEDOWN_VERSION "3.0.7"
|
||||||
|
#define HOEDOWN_VERSION_MAJOR 3
|
||||||
|
#define HOEDOWN_VERSION_MINOR 0
|
||||||
|
#define HOEDOWN_VERSION_REVISION 7
|
||||||
|
|
||||||
|
|
||||||
|
/*************
|
||||||
|
* FUNCTIONS *
|
||||||
|
*************/
|
||||||
|
|
||||||
|
/* hoedown_version: retrieve Hoedown's version numbers */
|
||||||
|
void hoedown_version(int *major, int *minor, int *revision);
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /** HOEDOWN_VERSION_H **/
|
|
@ -1 +1 @@
|
||||||
Subproject commit be7d25ed22abd07a254bfb8ff6c30de4fcc79e6a
|
Subproject commit b467b0816f5f6816778f90b55a9d0b5437310fd5
|
90
js/md.js
90
js/md.js
|
@ -1,90 +0,0 @@
|
||||||
.pragma library
|
|
||||||
|
|
||||||
var preg_replace=function(a,b,c,d){void 0===d&&(d=-1);var e=a.substr(a.lastIndexOf(a[0])+1),f=a.substr(1,a.lastIndexOf(a[0])-1),g=RegExp(f,e),i=[],j=0,k=0,l=c,m=[];if(-1===d){do m=g.exec(c),null!==m&&i.push(m);while(null!==m&&-1!==e.indexOf("g"))}else i.push(g.exec(c));for(j=i.length-1;j>-1;j--){for(m=b,k=i[j].length;k>-1;k--)m=m.replace("${"+k+"}",i[j][k]).replace("$"+k,i[j][k]).replace("\\"+k,i[j][k]);l=l.replace(i[j][0],m)}return l};
|
|
||||||
|
|
||||||
var markdown_parser = function(str){
|
|
||||||
|
|
||||||
var rules = [
|
|
||||||
// headers
|
|
||||||
['/(#+)(.*)/g', function(chars, header){
|
|
||||||
var level = chars.length;
|
|
||||||
return '<h'+level+'>'+header.trim()+'</h'+level+'>';
|
|
||||||
}],
|
|
||||||
// images
|
|
||||||
// ['/\\!\\[([^\\[]+)\\]\\(([^\\(]+)\\)/g', '<img src=\"\\2\" alt=\"\\1\" />'],
|
|
||||||
// link
|
|
||||||
['/\\[([^\\[]+)\\]\\(([^\\(]+)\\)/g', '<a href=\"\\2\">\\1</a>'],
|
|
||||||
// bold
|
|
||||||
['/(\\*\\*|__)(.*?)\\1/g', '<strong>\\2</strong>'],
|
|
||||||
// emphasis
|
|
||||||
['/(\\*|_)(.*?)\\1/g', '<i>\\2</i>'],
|
|
||||||
// strike
|
|
||||||
['/(\\~\\~)(.*?)\\1/g', '<del>\\2</del>'],
|
|
||||||
// quote
|
|
||||||
['/\\:\\"(.*?)\\"\\:/g', '<q>\\1</q>'],
|
|
||||||
// unordered list
|
|
||||||
// ['/\\n\\*(.*)/g', function(item){
|
|
||||||
// return '<ul>\n<li>'+item.trim()+'</li>\n</ul>';
|
|
||||||
// }],
|
|
||||||
// ordered list
|
|
||||||
// ['/\\n[0-9]+\\.(.*)/g', function(item){
|
|
||||||
// return '<ol>\n<li>'+item.trim()+'</li>\n</ol>';
|
|
||||||
// }],
|
|
||||||
// blockquote
|
|
||||||
['/\\n\\>(.*)/g', function(str){
|
|
||||||
return '<blockquote>'+str.trim()+'</blockquote>';
|
|
||||||
}]
|
|
||||||
// paragraphs
|
|
||||||
// ['/\\n[^\\n]+\\n/g', function(line){
|
|
||||||
// line = line.trim();
|
|
||||||
// if(line[0] === '<'){
|
|
||||||
// return line;
|
|
||||||
// }
|
|
||||||
// return '\n<p>'+line+'</p>\n';
|
|
||||||
// }]
|
|
||||||
], fixes = [
|
|
||||||
['/<\\/ul>\n<ul>/g', '\n'],
|
|
||||||
['/<\\/ol>\n<ol>/g', '\n'],
|
|
||||||
['/<\\/blockquote>\n<blockquote>/g', "\n"]
|
|
||||||
];
|
|
||||||
|
|
||||||
var parse_line = function(str){
|
|
||||||
str = "\n" + str.trim() + "\n";
|
|
||||||
for(var i = 0, j = rules.length; i < j; i++){
|
|
||||||
if(typeof rules[i][1] == 'function') {
|
|
||||||
var _flag = rules[i][0].substr(rules[i][0].lastIndexOf(rules[i][0][0])+1),
|
|
||||||
_pattern = rules[i][0].substr(1, rules[i][0].lastIndexOf(rules[i][0][0])-1),
|
|
||||||
reg = new RegExp(_pattern, _flag);
|
|
||||||
|
|
||||||
var matches = reg.exec(str);
|
|
||||||
if(matches !== null){
|
|
||||||
if(matches.length > 1){
|
|
||||||
str = preg_replace(rules[i][0], rules[i][1](matches[1], matches[2]), str);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
str = preg_replace(rules[i][0], rules[i][1](matches[0]), str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
str = preg_replace(rules[i][0], rules[i][1], str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return str.trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
str = str.split('\n');
|
|
||||||
var rtn = [];
|
|
||||||
for(var i = 0, j = str.length; i < j; i++){
|
|
||||||
rtn.push(parse_line(str[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
rtn = rtn.join('\n');
|
|
||||||
|
|
||||||
for(i = 0, j = fixes.length; i < j; i++){
|
|
||||||
rtn = preg_replace(fixes[i][0], fixes[i][1], rtn);
|
|
||||||
}
|
|
||||||
|
|
||||||
return rtn;
|
|
||||||
};
|
|
12
js/util.js
12
js/util.js
|
@ -1,12 +0,0 @@
|
||||||
.pragma library
|
|
||||||
|
|
||||||
function pushToStack(stack, page) {
|
|
||||||
if(page && stack.currentItem !== page) {
|
|
||||||
if(stack.depth === 1) {
|
|
||||||
stack.replace(page)
|
|
||||||
} else {
|
|
||||||
stack.pop(null)
|
|
||||||
stack.replace(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
447
qml/main.qml
447
qml/main.qml
|
@ -1,20 +1,21 @@
|
||||||
import QtQuick 2.9
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.3
|
import QtQuick.Layouts 1.12
|
||||||
import QtQuick.Controls.Material 2.2
|
import QtQuick.Controls.Material 2.12
|
||||||
import Qt.labs.settings 1.0
|
import Qt.labs.settings 1.0
|
||||||
import Qt.labs.platform 1.0 as Platform
|
import Qt.labs.platform 1.0 as Platform
|
||||||
|
|
||||||
|
import Spectral.Panel 2.0
|
||||||
import Spectral.Component 2.0
|
import Spectral.Component 2.0
|
||||||
import Spectral.Page 2.0
|
import Spectral.Page 2.0
|
||||||
|
import Spectral.Effect 2.0
|
||||||
|
|
||||||
import Spectral 0.1
|
import Spectral 0.1
|
||||||
import Spectral.Setting 0.1
|
import Spectral.Setting 0.1
|
||||||
|
|
||||||
import "qrc:/js/util.js" as Util
|
|
||||||
|
|
||||||
ApplicationWindow {
|
ApplicationWindow {
|
||||||
readonly property var currentConnection: accountListView.currentConnection ? accountListView.currentConnection : null
|
Material.theme: MPalette.theme
|
||||||
|
Material.background: MPalette.background
|
||||||
|
|
||||||
width: 960
|
width: 960
|
||||||
height: 640
|
height: 640
|
||||||
|
@ -26,9 +27,9 @@ ApplicationWindow {
|
||||||
visible: true
|
visible: true
|
||||||
title: qsTr("Spectral")
|
title: qsTr("Spectral")
|
||||||
|
|
||||||
Material.theme: MSettings.darkTheme ? Material.Dark : Material.Light
|
background: Rectangle {
|
||||||
|
color: MSettings.darkTheme ? "#303030" : "#FFFFFF"
|
||||||
Material.accent: spectralController.color(currentConnection ? currentConnection.localUserId : "")
|
}
|
||||||
|
|
||||||
Platform.SystemTrayIcon {
|
Platform.SystemTrayIcon {
|
||||||
visible: MSettings.showTray
|
visible: MSettings.showTray
|
||||||
|
@ -54,353 +55,147 @@ ApplicationWindow {
|
||||||
quitOnLastWindowClosed: !MSettings.showTray
|
quitOnLastWindowClosed: !MSettings.showTray
|
||||||
|
|
||||||
onNotificationClicked: {
|
onNotificationClicked: {
|
||||||
roomPage.enteredRoom = currentConnection.room(roomId)
|
roomListForm.enteredRoom = spectralController.connection.room(roomId)
|
||||||
roomPage.goToEvent(eventId)
|
roomForm.goToEvent(eventId)
|
||||||
showWindow()
|
showWindow()
|
||||||
}
|
}
|
||||||
onErrorOccured: {
|
onErrorOccured: {
|
||||||
errorDialog.error = error
|
roomListForm.errorControl.error = error
|
||||||
errorDialog.detail = detail
|
roomListForm.errorControl.detail = detail
|
||||||
errorDialog.open()
|
roomListForm.errorControl.visible = true
|
||||||
}
|
}
|
||||||
|
onSyncDone: roomListForm.errorControl.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
AccountListModel {
|
Shortcut {
|
||||||
id: accountListModel
|
sequence: StandardKey.Quit
|
||||||
controller: spectralController
|
onActivated: Qt.quit()
|
||||||
}
|
}
|
||||||
|
|
||||||
Dialog {
|
Dialog {
|
||||||
property string error
|
property bool busy: false
|
||||||
property string detail
|
|
||||||
|
|
||||||
|
width: 360
|
||||||
x: (window.width - width) / 2
|
x: (window.width - width) / 2
|
||||||
y: (window.height - height) / 2
|
y: (window.height - height) / 2
|
||||||
|
|
||||||
id: errorDialog
|
id: loginDialog
|
||||||
|
|
||||||
title: error + " Error"
|
parent: ApplicationWindow.overlay
|
||||||
contentItem: Label { text: errorDialog.detail }
|
|
||||||
}
|
|
||||||
|
|
||||||
Component {
|
title: "Login"
|
||||||
id: loginPage
|
|
||||||
|
|
||||||
Login { controller: spectralController }
|
contentItem: Column {
|
||||||
}
|
AutoTextField {
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
Room {
|
id: serverField
|
||||||
id: roomPage
|
|
||||||
|
|
||||||
parent: null
|
placeholderText: "Server Address"
|
||||||
|
text: "https://matrix.org"
|
||||||
|
}
|
||||||
|
|
||||||
connection: currentConnection
|
AutoTextField {
|
||||||
}
|
width: parent.width
|
||||||
|
|
||||||
Setting {
|
id: usernameField
|
||||||
id: settingPage
|
|
||||||
|
|
||||||
parent: null
|
placeholderText: "Username"
|
||||||
|
}
|
||||||
|
|
||||||
listModel: accountListModel
|
AutoTextField {
|
||||||
}
|
width: parent.width
|
||||||
|
|
||||||
RowLayout {
|
id: passwordField
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
Rectangle {
|
placeholderText: "Password"
|
||||||
Layout.preferredWidth: 64
|
echoMode: TextInput.Password
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: sideNav
|
|
||||||
|
|
||||||
color: Material.primary
|
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
anchors.fill: parent
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
AutoListView {
|
|
||||||
property var currentConnection: null
|
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.fillHeight: true
|
|
||||||
|
|
||||||
id: accountListView
|
|
||||||
|
|
||||||
model: accountListModel
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
delegate: Column {
|
|
||||||
property bool expanded: accountListView.currentConnection === connection
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
|
|
||||||
SideNavButton {
|
|
||||||
width: parent.width
|
|
||||||
height: width
|
|
||||||
|
|
||||||
selected: stackView.currentItem === page && currentConnection === connection
|
|
||||||
|
|
||||||
ImageItem {
|
|
||||||
anchors.fill: parent
|
|
||||||
anchors.margins: 12
|
|
||||||
|
|
||||||
hint: user.displayName
|
|
||||||
source: user.paintable
|
|
||||||
}
|
|
||||||
|
|
||||||
highlightColor: spectralController.color(user.id)
|
|
||||||
|
|
||||||
page: roomPage
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
accountListView.currentConnection = connection
|
|
||||||
roomPage.filter = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column {
|
|
||||||
width: parent.width
|
|
||||||
height: expanded ? implicitHeight : 0
|
|
||||||
|
|
||||||
spacing: 0
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
SideNavButton {
|
|
||||||
width: parent.width
|
|
||||||
height: width
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue7f7"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: roomPage.filter = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
SideNavButton {
|
|
||||||
width: parent.width
|
|
||||||
height: width
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue7fd"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: roomPage.filter = 2
|
|
||||||
}
|
|
||||||
|
|
||||||
SideNavButton {
|
|
||||||
width: parent.width
|
|
||||||
height: width
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue7fb"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: roomPage.filter = 3
|
|
||||||
}
|
|
||||||
|
|
||||||
Behavior on height {
|
|
||||||
PropertyAnimation { easing.type: Easing.InOutCubic; duration: 200 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SideNavButton {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: width
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue145"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
enabled: !addRoomMenu.opened
|
|
||||||
onClicked: addRoomMenu.popup()
|
|
||||||
|
|
||||||
Menu {
|
|
||||||
id: addRoomMenu
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text:"New Room"
|
|
||||||
onTriggered: addRoomDialog.open()
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
id: addRoomDialog
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 360
|
|
||||||
|
|
||||||
title: "New Room"
|
|
||||||
modal: true
|
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
||||||
|
|
||||||
contentItem: Column {
|
|
||||||
AutoTextField {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
id: addRoomDialogNameTextField
|
|
||||||
|
|
||||||
placeholderText: "Name"
|
|
||||||
}
|
|
||||||
AutoTextField {
|
|
||||||
width: parent.width
|
|
||||||
|
|
||||||
id: addRoomDialogTopicTextField
|
|
||||||
|
|
||||||
placeholderText: "Topic"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: spectralController.createRoom(currentConnection, addRoomDialogNameTextField.text, addRoomDialogTopicTextField.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: "Join Room"
|
|
||||||
|
|
||||||
onTriggered: joinRoomDialog.open()
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 360
|
|
||||||
|
|
||||||
id: joinRoomDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
title: "Input Room Alias or ID"
|
|
||||||
modal: true
|
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
||||||
|
|
||||||
contentItem: AutoTextField {
|
|
||||||
id: joinRoomDialogTextField
|
|
||||||
placeholderText: "#matrix:matrix.org"
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: spectralController.joinRoom(currentConnection, joinRoomDialogTextField.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
MenuItem {
|
|
||||||
text: "Direct Chat"
|
|
||||||
|
|
||||||
onTriggered: directChatDialog.open()
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 360
|
|
||||||
|
|
||||||
id: directChatDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
title: "Input User ID"
|
|
||||||
modal: true
|
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
||||||
|
|
||||||
contentItem: AutoTextField {
|
|
||||||
id: directChatDialogTextField
|
|
||||||
placeholderText: "@bot:matrix.org"
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: spectralController.createDirectChat(currentConnection, directChatDialogTextField.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SideNavButton {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: width
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue8b8"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
page: settingPage
|
|
||||||
}
|
|
||||||
|
|
||||||
SideNavButton {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.preferredHeight: width
|
|
||||||
|
|
||||||
MaterialIcon {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
icon: "\ue8ac"
|
|
||||||
color: "white"
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: MSettings.confirmOnExit ? confirmExitDialog.open() : Qt.quit()
|
|
||||||
|
|
||||||
Dialog {
|
|
||||||
x: (window.width - width) / 2
|
|
||||||
y: (window.height - height) / 2
|
|
||||||
width: 360
|
|
||||||
|
|
||||||
id: confirmExitDialog
|
|
||||||
|
|
||||||
parent: ApplicationWindow.overlay
|
|
||||||
|
|
||||||
title: "Exit"
|
|
||||||
modal: true
|
|
||||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
|
||||||
|
|
||||||
contentItem: Column {
|
|
||||||
Label { text: "Exit?" }
|
|
||||||
CheckBox {
|
|
||||||
text: "Do not ask next time"
|
|
||||||
checked: !MSettings.confirmOnExit
|
|
||||||
|
|
||||||
onCheckedChanged: MSettings.confirmOnExit = !checked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onAccepted: Qt.quit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StackView {
|
footer: DialogButtonBox {
|
||||||
|
Button {
|
||||||
|
text: "OK"
|
||||||
|
flat: true
|
||||||
|
enabled: !loginDialog.busy
|
||||||
|
|
||||||
|
onClicked: loginDialog.doLogin()
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
text: "Cancel"
|
||||||
|
flat: true
|
||||||
|
enabled: !loginDialog.busy
|
||||||
|
|
||||||
|
onClicked: loginDialog.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
ToolTip {
|
||||||
|
id: loginButtonTooltip
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) spectralController.onErrorOccured.connect(showError)
|
||||||
|
else spectralController.onErrorOccured.disconnect(showError)
|
||||||
|
}
|
||||||
|
|
||||||
|
function showError(error, detail) {
|
||||||
|
loginDialog.busy = false
|
||||||
|
loginButtonTooltip.text = error + ": " + detail
|
||||||
|
loginButtonTooltip.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
function doLogin() {
|
||||||
|
if (!(serverField.text.startsWith("http") && serverField.text.includes("://"))) {
|
||||||
|
loginButtonTooltip.text = "Server address should start with http(s)://"
|
||||||
|
loginButtonTooltip.open()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
loginDialog.busy = true
|
||||||
|
spectralController.loginWithCredentials(serverField.text, usernameField.text, passwordField.text)
|
||||||
|
|
||||||
|
spectralController.connectionAdded.connect(function(conn) {
|
||||||
|
busy = false
|
||||||
|
loginDialog.close()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SplitView {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
RoomListPanel {
|
||||||
|
width: window.width * 0.35
|
||||||
|
Layout.minimumWidth: 180
|
||||||
|
|
||||||
|
id: roomListForm
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
controller: spectralController
|
||||||
|
|
||||||
|
onLeaveRoom: roomForm.saveReadMarker(room)
|
||||||
|
}
|
||||||
|
|
||||||
|
RoomPanel {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.minimumWidth: 480
|
||||||
|
|
||||||
id: stackView
|
id: roomForm
|
||||||
|
|
||||||
initialItem: roomPage
|
clip: true
|
||||||
|
|
||||||
|
currentRoom: roomListForm.enteredRoom
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding {
|
Binding {
|
||||||
target: imageProvider
|
target: imageProvider
|
||||||
property: "connection"
|
property: "connection"
|
||||||
value: currentConnection
|
value: spectralController.connection
|
||||||
}
|
}
|
||||||
|
|
||||||
function showWindow() {
|
function showWindow() {
|
||||||
|
@ -413,9 +208,9 @@ ApplicationWindow {
|
||||||
window.hide()
|
window.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
spectralController.initiated.connect(function() {
|
spectralController.initiated.connect(function() {
|
||||||
if (spectralController.accountCount == 0) stackView.push(loginPage)
|
if (spectralController.accountCount == 0) loginDialog.open()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ Style=Material
|
||||||
|
|
||||||
[Material]
|
[Material]
|
||||||
Theme=Light
|
Theme=Light
|
||||||
|
Variant=Dense
|
||||||
Primary=#344955
|
Primary=#344955
|
||||||
Accent=#498882
|
Accent=#673AB7
|
||||||
;Foreground=Black
|
Font/Family="Roboto,Noto Sans,Noto Color Emoji"
|
||||||
;Background=#161616
|
|
||||||
|
|
32
res.qrc
32
res.qrc
|
@ -2,30 +2,19 @@
|
||||||
<qresource prefix="/">
|
<qresource prefix="/">
|
||||||
<file>qtquickcontrols2.conf</file>
|
<file>qtquickcontrols2.conf</file>
|
||||||
<file>qml/main.qml</file>
|
<file>qml/main.qml</file>
|
||||||
<file>js/md.js</file>
|
|
||||||
<file>js/util.js</file>
|
|
||||||
<file>imports/Spectral/Component/Emoji/EmojiPicker.qml</file>
|
<file>imports/Spectral/Component/Emoji/EmojiPicker.qml</file>
|
||||||
<file>imports/Spectral/Component/Emoji/qmldir</file>
|
<file>imports/Spectral/Component/Emoji/qmldir</file>
|
||||||
<file>imports/Spectral/Component/Timeline/DownloadableContent.qml</file>
|
<file>imports/Spectral/Component/Timeline/DownloadableContent.qml</file>
|
||||||
<file>imports/Spectral/Component/Timeline/GenericBubble.qml</file>
|
|
||||||
<file>imports/Spectral/Component/Timeline/MessageDelegate.qml</file>
|
<file>imports/Spectral/Component/Timeline/MessageDelegate.qml</file>
|
||||||
<file>imports/Spectral/Component/Timeline/qmldir</file>
|
<file>imports/Spectral/Component/Timeline/qmldir</file>
|
||||||
<file>imports/Spectral/Component/Timeline/StateDelegate.qml</file>
|
<file>imports/Spectral/Component/Timeline/StateDelegate.qml</file>
|
||||||
<file>imports/Spectral/Component/AutoMouseArea.qml</file>
|
<file>imports/Spectral/Component/AutoMouseArea.qml</file>
|
||||||
<file>imports/Spectral/Component/MaterialIcon.qml</file>
|
<file>imports/Spectral/Component/MaterialIcon.qml</file>
|
||||||
<file>imports/Spectral/Component/qmldir</file>
|
<file>imports/Spectral/Component/qmldir</file>
|
||||||
<file>imports/Spectral/Component/SideNavButton.qml</file>
|
|
||||||
<file>imports/Spectral/Effect/ElevationEffect.qml</file>
|
<file>imports/Spectral/Effect/ElevationEffect.qml</file>
|
||||||
<file>imports/Spectral/Effect/qmldir</file>
|
<file>imports/Spectral/Effect/qmldir</file>
|
||||||
<file>imports/Spectral/Menu/MessageContextMenu.qml</file>
|
|
||||||
<file>imports/Spectral/Menu/qmldir</file>
|
|
||||||
<file>imports/Spectral/Menu/RoomContextMenu.qml</file>
|
|
||||||
<file>imports/Spectral/Page/Login.qml</file>
|
|
||||||
<file>imports/Spectral/Page/qmldir</file>
|
<file>imports/Spectral/Page/qmldir</file>
|
||||||
<file>imports/Spectral/Page/Room.qml</file>
|
|
||||||
<file>assets/font/material.ttf</file>
|
<file>assets/font/material.ttf</file>
|
||||||
<file>assets/img/avatar.png</file>
|
|
||||||
<file>assets/img/background.jpg</file>
|
|
||||||
<file>assets/img/icon.icns</file>
|
<file>assets/img/icon.icns</file>
|
||||||
<file>assets/img/icon.ico</file>
|
<file>assets/img/icon.ico</file>
|
||||||
<file>assets/img/icon.png</file>
|
<file>assets/img/icon.png</file>
|
||||||
|
@ -33,25 +22,26 @@
|
||||||
<file>imports/Spectral/Font/MaterialFont.qml</file>
|
<file>imports/Spectral/Font/MaterialFont.qml</file>
|
||||||
<file>imports/Spectral/Font/qmldir</file>
|
<file>imports/Spectral/Font/qmldir</file>
|
||||||
<file>imports/Spectral/Setting/qmldir</file>
|
<file>imports/Spectral/Setting/qmldir</file>
|
||||||
<file>imports/Spectral/Page/Setting.qml</file>
|
|
||||||
<file>imports/Spectral/Page/SettingForm.ui.qml</file>
|
|
||||||
<file>imports/Spectral/Page/SettingCategoryDelegate.qml</file>
|
|
||||||
<file>imports/Spectral/Page/SettingAccountDelegate.qml</file>
|
|
||||||
<file>imports/Spectral/Page/LoginForm.ui.qml</file>
|
|
||||||
<file>imports/Spectral/Panel/qmldir</file>
|
<file>imports/Spectral/Panel/qmldir</file>
|
||||||
<file>imports/Spectral/Panel/RoomDrawer.qml</file>
|
<file>imports/Spectral/Panel/RoomDrawer.qml</file>
|
||||||
<file>imports/Spectral/Panel/RoomListPanel.qml</file>
|
<file>imports/Spectral/Panel/RoomListPanel.qml</file>
|
||||||
<file>imports/Spectral/Panel/RoomListPanelForm.ui.qml</file>
|
|
||||||
<file>imports/Spectral/Panel/RoomPanel.qml</file>
|
<file>imports/Spectral/Panel/RoomPanel.qml</file>
|
||||||
<file>imports/Spectral/Panel/RoomPanelForm.ui.qml</file>
|
|
||||||
<file>imports/Spectral/Panel/RoomHeader.qml</file>
|
<file>imports/Spectral/Panel/RoomHeader.qml</file>
|
||||||
<file>imports/Spectral/Panel/RoomListDelegate.qml</file>
|
|
||||||
<file>imports/Spectral/Component/ScrollHelper.qml</file>
|
<file>imports/Spectral/Component/ScrollHelper.qml</file>
|
||||||
<file>imports/Spectral/Component/AutoListView.qml</file>
|
<file>imports/Spectral/Component/AutoListView.qml</file>
|
||||||
<file>imports/Spectral/Component/Timeline/TimelineImage.qml</file>
|
|
||||||
<file>imports/Spectral/Component/Timeline/TimelineLabel.qml</file>
|
|
||||||
<file>imports/Spectral/Component/AutoTextField.qml</file>
|
<file>imports/Spectral/Component/AutoTextField.qml</file>
|
||||||
<file>imports/Spectral/Panel/RoomPanelInput.qml</file>
|
<file>imports/Spectral/Panel/RoomPanelInput.qml</file>
|
||||||
<file>imports/Spectral/Component/SplitView.qml</file>
|
<file>imports/Spectral/Component/SplitView.qml</file>
|
||||||
|
<file>imports/Spectral/Font/CommonFont.qml</file>
|
||||||
|
<file>imports/Spectral/Component/Timeline/SectionDelegate.qml</file>
|
||||||
|
<file>assets/img/roompanel.svg</file>
|
||||||
|
<file>assets/img/matrix.svg</file>
|
||||||
|
<file>imports/Spectral/Effect/RippleEffect.qml</file>
|
||||||
|
<file>imports/Spectral/Effect/CircleMask.qml</file>
|
||||||
|
<file>assets/img/roompanel-dark.svg</file>
|
||||||
|
<file>imports/Spectral/Component/Timeline/ImageDelegate.qml</file>
|
||||||
|
<file>imports/Spectral/Component/Avatar.qml</file>
|
||||||
|
<file>imports/Spectral/Setting/Palette.qml</file>
|
||||||
|
<file>imports/Spectral/Component/Timeline/FileDelegate.qml</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
||||||
|
|
42
spectral.pro
42
spectral.pro
|
@ -20,6 +20,9 @@ isEmpty(USE_SYSTEM_SORTFILTERPROXYMODEL) {
|
||||||
isEmpty(USE_SYSTEM_QMATRIXCLIENT) {
|
isEmpty(USE_SYSTEM_QMATRIXCLIENT) {
|
||||||
USE_SYSTEM_QMATRIXCLIENT = false
|
USE_SYSTEM_QMATRIXCLIENT = false
|
||||||
}
|
}
|
||||||
|
isEmpty(BUNDLE_FONT) {
|
||||||
|
BUNDLE_FONT = false
|
||||||
|
}
|
||||||
|
|
||||||
$$USE_SYSTEM_QMATRIXCLIENT {
|
$$USE_SYSTEM_QMATRIXCLIENT {
|
||||||
PKGCONFIG += QMatrixClient
|
PKGCONFIG += QMatrixClient
|
||||||
|
@ -34,6 +37,27 @@ $$USE_SYSTEM_SORTFILTERPROXYMODEL {
|
||||||
include(include/SortFilterProxyModel/SortFilterProxyModel.pri)
|
include(include/SortFilterProxyModel/SortFilterProxyModel.pri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
INCLUDEPATH += include/hoedown
|
||||||
|
HEADERS += \
|
||||||
|
include/hoedown/autolink.h \
|
||||||
|
include/hoedown/buffer.h \
|
||||||
|
include/hoedown/document.h \
|
||||||
|
include/hoedown/escape.h \
|
||||||
|
include/hoedown/html.h \
|
||||||
|
include/hoedown/stack.h \
|
||||||
|
include/hoedown/version.h
|
||||||
|
|
||||||
|
SOURCES += \
|
||||||
|
include/hoedown/autolink.c \
|
||||||
|
include/hoedown/buffer.c \
|
||||||
|
include/hoedown/document.c \
|
||||||
|
include/hoedown/escape.c \
|
||||||
|
include/hoedown/html.c \
|
||||||
|
include/hoedown/html_blocks.c \
|
||||||
|
include/hoedown/html_smartypants.c \
|
||||||
|
include/hoedown/stack.c \
|
||||||
|
include/hoedown/version.c
|
||||||
|
|
||||||
# The following define makes your compiler emit warnings if you use
|
# The following define makes your compiler emit warnings if you use
|
||||||
# any feature of Qt which as been marked deprecated (the exact warnings
|
# any feature of Qt which as been marked deprecated (the exact warnings
|
||||||
# depend on your compiler). Please consult the documentation of the
|
# depend on your compiler). Please consult the documentation of the
|
||||||
|
@ -45,8 +69,14 @@ DEFINES += QT_DEPRECATED_WARNINGS
|
||||||
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
# You can also select to disable deprecated APIs only up to a certain version of Qt.
|
||||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||||
|
|
||||||
RESOURCES += \
|
RESOURCES += res.qrc
|
||||||
res.qrc
|
$$BUNDLE_FONT {
|
||||||
|
message("Bundling fonts.")
|
||||||
|
DEFINES += BUNDLE_FONT
|
||||||
|
RESOURCES += font.qrc
|
||||||
|
} else {
|
||||||
|
message("Using fonts from operating system.")
|
||||||
|
}
|
||||||
|
|
||||||
# Additional import path used to resolve QML modules in Qt Creator's code model
|
# Additional import path used to resolve QML modules in Qt Creator's code model
|
||||||
QML_IMPORT_PATH += imports/
|
QML_IMPORT_PATH += imports/
|
||||||
|
@ -102,12 +132,10 @@ HEADERS += \
|
||||||
src/emojimodel.h \
|
src/emojimodel.h \
|
||||||
src/spectralroom.h \
|
src/spectralroom.h \
|
||||||
src/userlistmodel.h \
|
src/userlistmodel.h \
|
||||||
src/imageitem.h \
|
|
||||||
src/accountlistmodel.h \
|
src/accountlistmodel.h \
|
||||||
src/spectraluser.h \
|
src/spectraluser.h \
|
||||||
src/notifications/manager.h \
|
src/notifications/manager.h \
|
||||||
src/utils.h \
|
src/utils.h
|
||||||
src/paintable.h
|
|
||||||
|
|
||||||
SOURCES += src/main.cpp \
|
SOURCES += src/main.cpp \
|
||||||
src/controller.cpp \
|
src/controller.cpp \
|
||||||
|
@ -117,11 +145,9 @@ SOURCES += src/main.cpp \
|
||||||
src/emojimodel.cpp \
|
src/emojimodel.cpp \
|
||||||
src/spectralroom.cpp \
|
src/spectralroom.cpp \
|
||||||
src/userlistmodel.cpp \
|
src/userlistmodel.cpp \
|
||||||
src/imageitem.cpp \
|
|
||||||
src/accountlistmodel.cpp \
|
src/accountlistmodel.cpp \
|
||||||
src/spectraluser.cpp \
|
src/spectraluser.cpp \
|
||||||
src/utils.cpp \
|
src/utils.cpp
|
||||||
src/paintable.cpp
|
|
||||||
|
|
||||||
unix:!mac {
|
unix:!mac {
|
||||||
SOURCES += src/notifications/managerlinux.cpp
|
SOURCES += src/notifications/managerlinux.cpp
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include "csapi/joining.h"
|
#include "csapi/joining.h"
|
||||||
#include "csapi/logout.h"
|
#include "csapi/logout.h"
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
#include <QClipboard>
|
#include <QClipboard>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
@ -42,8 +44,8 @@ Controller::Controller(QObject* parent)
|
||||||
|
|
||||||
Controller::~Controller() {
|
Controller::~Controller() {
|
||||||
for (Connection* c : m_connections) {
|
for (Connection* c : m_connections) {
|
||||||
c->saveState();
|
|
||||||
c->stopSync();
|
c->stopSync();
|
||||||
|
c->saveState();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,29 +60,29 @@ inline QString accessTokenFileName(const AccountSettings& account) {
|
||||||
void Controller::loginWithCredentials(QString serverAddr, QString user,
|
void Controller::loginWithCredentials(QString serverAddr, QString user,
|
||||||
QString pass) {
|
QString pass) {
|
||||||
if (!user.isEmpty() && !pass.isEmpty()) {
|
if (!user.isEmpty() && !pass.isEmpty()) {
|
||||||
Connection* m_connection = new Connection(this);
|
Connection* conn = new Connection(this);
|
||||||
m_connection->setHomeserver(QUrl(serverAddr));
|
conn->setHomeserver(QUrl(serverAddr));
|
||||||
m_connection->connectToServer(user, pass, "");
|
conn->connectToServer(user, pass, "");
|
||||||
connect(m_connection, &Connection::connected, [=] {
|
connect(conn, &Connection::connected, [=] {
|
||||||
AccountSettings account(m_connection->userId());
|
AccountSettings account(conn->userId());
|
||||||
account.setKeepLoggedIn(true);
|
account.setKeepLoggedIn(true);
|
||||||
account.clearAccessToken(); // Drop the legacy - just in case
|
account.clearAccessToken(); // Drop the legacy - just in case
|
||||||
account.setHomeserver(m_connection->homeserver());
|
account.setHomeserver(conn->homeserver());
|
||||||
account.setDeviceId(m_connection->deviceId());
|
account.setDeviceId(conn->deviceId());
|
||||||
account.setDeviceName("Spectral");
|
account.setDeviceName("Spectral");
|
||||||
if (!saveAccessToken(account, m_connection->accessToken()))
|
if (!saveAccessToken(account, conn->accessToken()))
|
||||||
qWarning() << "Couldn't save access token";
|
qWarning() << "Couldn't save access token";
|
||||||
account.sync();
|
account.sync();
|
||||||
addConnection(m_connection);
|
addConnection(conn);
|
||||||
|
setConnection(conn);
|
||||||
});
|
});
|
||||||
connect(m_connection, &Connection::networkError,
|
connect(conn, &Connection::networkError,
|
||||||
[=](QString error, QByteArray detail) {
|
[=](QString error, QString, int, int) {
|
||||||
emit errorOccured("Network", error);
|
emit errorOccured("Network Error", error);
|
||||||
});
|
|
||||||
connect(m_connection, &Connection::loginError,
|
|
||||||
[=](QString error, QByteArray detail) {
|
|
||||||
emit errorOccured("Login Failed", error);
|
|
||||||
});
|
});
|
||||||
|
connect(conn, &Connection::loginError, [=](QString error, QString) {
|
||||||
|
emit errorOccured("Login Failed", error);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,6 +100,7 @@ void Controller::logout(Connection* conn) {
|
||||||
conn->stopSync();
|
conn->stopSync();
|
||||||
emit conn->stateChanged();
|
emit conn->stateChanged();
|
||||||
emit conn->loggedOut();
|
emit conn->loggedOut();
|
||||||
|
if (!m_connections.isEmpty()) setConnection(m_connections[0]);
|
||||||
});
|
});
|
||||||
connect(job, &LogoutJob::failure, this, [=] {
|
connect(job, &LogoutJob::failure, this, [=] {
|
||||||
emit errorOccured("Server-side Logout Failed", job->errorString());
|
emit errorOccured("Server-side Logout Failed", job->errorString());
|
||||||
|
@ -109,11 +112,12 @@ void Controller::addConnection(Connection* c) {
|
||||||
|
|
||||||
m_connections.push_back(c);
|
m_connections.push_back(c);
|
||||||
|
|
||||||
connect(c, &Connection::syncDone, this, [=] {
|
c->setLazyLoading(true);
|
||||||
c->sync(30000);
|
|
||||||
|
|
||||||
static int counter = 0;
|
connect(c, &Connection::syncDone, this, [=] {
|
||||||
if (++counter % 17 == 2) c->saveState();
|
emit syncDone();
|
||||||
|
c->sync(30000);
|
||||||
|
c->saveState();
|
||||||
});
|
});
|
||||||
connect(c, &Connection::loggedOut, this, [=] { dropConnection(c); });
|
connect(c, &Connection::loggedOut, this, [=] { dropConnection(c); });
|
||||||
|
|
||||||
|
@ -146,17 +150,17 @@ void Controller::invokeLogin() {
|
||||||
c->loadState();
|
c->loadState();
|
||||||
addConnection(c);
|
addConnection(c);
|
||||||
});
|
});
|
||||||
connect(c, &Connection::loginError,
|
connect(c, &Connection::loginError, [=](QString error, QString) {
|
||||||
[=](QString error, QByteArray detail) {
|
emit errorOccured("Login Failed", error);
|
||||||
emit errorOccured("Login Failed", error);
|
});
|
||||||
});
|
|
||||||
connect(c, &Connection::networkError,
|
connect(c, &Connection::networkError,
|
||||||
[=](QString error, QByteArray detail) {
|
[=](QString error, QString, int, int) {
|
||||||
emit errorOccured("Network", error);
|
emit errorOccured("Network Error", error);
|
||||||
});
|
});
|
||||||
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
c->connectWithToken(account.userId(), accessToken, account.deviceId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!m_connections.isEmpty()) setConnection(m_connections[0]);
|
||||||
emit initiated();
|
emit initiated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +188,7 @@ bool Controller::saveAccessToken(const AccountSettings& account,
|
||||||
auto fileDir = QFileInfo(accountTokenFile).dir();
|
auto fileDir = QFileInfo(accountTokenFile).dir();
|
||||||
if (!((fileDir.exists() || fileDir.mkpath(".")) &&
|
if (!((fileDir.exists() || fileDir.mkpath(".")) &&
|
||||||
accountTokenFile.open(QFile::WriteOnly))) {
|
accountTokenFile.open(QFile::WriteOnly))) {
|
||||||
emit errorOccured("Token", "Cannot save access token.");
|
emit errorOccured("I/O Denied", "Cannot save access token.");
|
||||||
} else {
|
} else {
|
||||||
accountTokenFile.write(accessToken);
|
accountTokenFile.write(accessToken);
|
||||||
return true;
|
return true;
|
||||||
|
@ -227,19 +231,22 @@ void Controller::playAudio(QUrl localFile) {
|
||||||
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
|
connect(player, &QMediaPlayer::stateChanged, [=] { player->deleteLater(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor Controller::color(QString userId) {
|
|
||||||
return QColor(SettingsGroup("UI/Color").value(userId, "#498882").toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::setColor(QString userId, QColor newColor) {
|
|
||||||
SettingsGroup("UI/Color").setValue(userId, newColor.name());
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::postNotification(const QString& roomId, const QString& eventId,
|
void Controller::postNotification(const QString& roomId, const QString& eventId,
|
||||||
const QString& roomName,
|
const QString& roomName,
|
||||||
const QString& senderName,
|
const QString& senderName,
|
||||||
const QString& text, const QImage& icon,
|
const QString& text, const QImage& icon) {
|
||||||
const QUrl& iconPath) {
|
|
||||||
notificationsManager.postNotification(roomId, eventId, roomName, senderName,
|
notificationsManager.postNotification(roomId, eventId, roomName, senderName,
|
||||||
text, icon, iconPath);
|
text, icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
int Controller::dpi() {
|
||||||
|
return SettingsGroup("Interface").value("dpi", 100).toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Controller::setDpi(int dpi) {
|
||||||
|
SettingsGroup("Interface").setValue("dpi", dpi);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString Controller::removeReply(const QString& text) {
|
||||||
|
return utils::removeReply(text);
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,8 @@ class Controller : public QObject {
|
||||||
connectionDropped)
|
connectionDropped)
|
||||||
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE
|
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE
|
||||||
setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
||||||
|
Q_PROPERTY(Connection* connection READ connection WRITE setConnection NOTIFY
|
||||||
|
connectionChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit Controller(QObject* parent = nullptr);
|
explicit Controller(QObject* parent = nullptr);
|
||||||
|
@ -30,6 +32,9 @@ class Controller : public QObject {
|
||||||
|
|
||||||
QVector<Connection*> connections() { return m_connections; }
|
QVector<Connection*> connections() { return m_connections; }
|
||||||
|
|
||||||
|
Q_INVOKABLE int dpi();
|
||||||
|
Q_INVOKABLE void setDpi(int dpi);
|
||||||
|
|
||||||
// All the non-Q_INVOKABLE functions.
|
// All the non-Q_INVOKABLE functions.
|
||||||
void addConnection(Connection* c);
|
void addConnection(Connection* c);
|
||||||
void dropConnection(Connection* c);
|
void dropConnection(Connection* c);
|
||||||
|
@ -47,13 +52,23 @@ class Controller : public QObject {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Q_INVOKABLE QColor color(QString userId);
|
Connection* connection() {
|
||||||
Q_INVOKABLE void setColor(QString userId, QColor newColor);
|
if (m_connection.isNull()) return nullptr;
|
||||||
|
return m_connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setConnection(Connection* conn) {
|
||||||
|
if (!conn) return;
|
||||||
|
if (conn == m_connection) return;
|
||||||
|
m_connection = conn;
|
||||||
|
emit connectionChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QClipboard* m_clipboard = QApplication::clipboard();
|
QClipboard* m_clipboard = QApplication::clipboard();
|
||||||
NotificationsManager notificationsManager;
|
NotificationsManager notificationsManager;
|
||||||
QVector<Connection*> m_connections;
|
QVector<Connection*> m_connections;
|
||||||
|
QPointer<Connection> m_connection;
|
||||||
|
|
||||||
QByteArray loadAccessToken(const AccountSettings& account);
|
QByteArray loadAccessToken(const AccountSettings& account);
|
||||||
bool saveAccessToken(const AccountSettings& account,
|
bool saveAccessToken(const AccountSettings& account,
|
||||||
|
@ -61,17 +76,21 @@ class Controller : public QObject {
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings() const;
|
void saveSettings() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE QString removeReply(const QString& text);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void invokeLogin();
|
void invokeLogin();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void busyChanged();
|
void busyChanged();
|
||||||
void errorOccured(QString error, QString detail);
|
void errorOccured(QString error, QString detail);
|
||||||
|
void syncDone();
|
||||||
void connectionAdded(Connection* conn);
|
void connectionAdded(Connection* conn);
|
||||||
void connectionDropped(Connection* conn);
|
void connectionDropped(Connection* conn);
|
||||||
void initiated();
|
void initiated();
|
||||||
void notificationClicked(const QString roomId, const QString eventId);
|
void notificationClicked(const QString roomId, const QString eventId);
|
||||||
void quitOnLastWindowClosedChanged();
|
void quitOnLastWindowClosedChanged();
|
||||||
|
void connectionChanged();
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void logout(Connection* conn);
|
void logout(Connection* conn);
|
||||||
|
@ -82,8 +101,7 @@ class Controller : public QObject {
|
||||||
void playAudio(QUrl localFile);
|
void playAudio(QUrl localFile);
|
||||||
void postNotification(const QString& roomId, const QString& eventId,
|
void postNotification(const QString& roomId, const QString& eventId,
|
||||||
const QString& roomName, const QString& senderName,
|
const QString& roomName, const QString& senderName,
|
||||||
const QString& text, const QImage& icon,
|
const QString& text, const QImage& icon);
|
||||||
const QUrl& iconPath);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // CONTROLLER_H
|
#endif // CONTROLLER_H
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
#include "imageitem.h"
|
|
||||||
|
|
||||||
#include <QBitmap>
|
|
||||||
#include <QGraphicsOpacityEffect>
|
|
||||||
#include <QRect>
|
|
||||||
|
|
||||||
ImageItem::ImageItem(QQuickItem *parent) : QQuickPaintedItem(parent) {}
|
|
||||||
|
|
||||||
inline static QString stringtoColor(QString string) {
|
|
||||||
int hash = 0;
|
|
||||||
for (int i = 0; i < string.length(); i++)
|
|
||||||
hash = string.at(i).unicode() + ((hash << 5) - hash);
|
|
||||||
QString colour = "#";
|
|
||||||
for (int j = 0; j < 3; j++)
|
|
||||||
colour += ("00" + QString::number((hash >> (j * 8)) & 0xFF, 16)).right(2);
|
|
||||||
return colour;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static QImage getImageFromPaintable(QPointer<Paintable> p, QRectF b) {
|
|
||||||
if (p.isNull()) return {};
|
|
||||||
QImage image(p->image(int(b.width()), int(b.height())));
|
|
||||||
if (image.isNull()) return {};
|
|
||||||
return image;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageItem::paint(QPainter *painter) {
|
|
||||||
QRectF bounding_rect = boundingRect();
|
|
||||||
|
|
||||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
|
||||||
|
|
||||||
QImage image(getImageFromPaintable(m_paintable, bounding_rect));
|
|
||||||
|
|
||||||
if (image.isNull()) {
|
|
||||||
painter->setPen(Qt::NoPen);
|
|
||||||
if (m_color.isEmpty())
|
|
||||||
painter->setBrush(QColor(stringtoColor(m_hint)));
|
|
||||||
else
|
|
||||||
painter->setBrush(QColor(m_color));
|
|
||||||
if (m_round)
|
|
||||||
painter->drawEllipse(0, 0, int(bounding_rect.width()),
|
|
||||||
int(bounding_rect.height()));
|
|
||||||
else
|
|
||||||
painter->drawRect(0, 0, int(bounding_rect.width()),
|
|
||||||
int(bounding_rect.height()));
|
|
||||||
painter->setPen(QPen(Qt::white, 2));
|
|
||||||
QFont font;
|
|
||||||
font.setStyleHint(QFont::SansSerif);
|
|
||||||
|
|
||||||
font.setPixelSize(int(bounding_rect.width() / 2));
|
|
||||||
font.setBold(true);
|
|
||||||
painter->setFont(font);
|
|
||||||
painter->drawText(
|
|
||||||
QRect(0, 0, int(bounding_rect.width()), int(bounding_rect.height())),
|
|
||||||
Qt::AlignCenter, m_hint.at(0).toUpper());
|
|
||||||
} else {
|
|
||||||
QImage scaled = image.scaled(
|
|
||||||
int(bounding_rect.width()) + 1, int(bounding_rect.height()) + 1,
|
|
||||||
Qt::KeepAspectRatioByExpanding, Qt::FastTransformation);
|
|
||||||
|
|
||||||
QPointF center = bounding_rect.center() - scaled.rect().center();
|
|
||||||
|
|
||||||
if (m_round) {
|
|
||||||
QPainterPath clip;
|
|
||||||
clip.addEllipse(
|
|
||||||
0, 0, bounding_rect.width(),
|
|
||||||
bounding_rect.height()); // this is the shape we want to clip to
|
|
||||||
painter->setClipPath(clip);
|
|
||||||
}
|
|
||||||
|
|
||||||
painter->drawImage(center, scaled);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageItem::setPaintable(Paintable *paintable) {
|
|
||||||
if (!paintable) return;
|
|
||||||
if (!m_paintable.isNull()) m_paintable->disconnect(this);
|
|
||||||
m_paintable = paintable;
|
|
||||||
connect(m_paintable, &Paintable::paintableChanged, this,
|
|
||||||
[=] { this->update(); });
|
|
||||||
emit paintableChanged();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageItem::setHint(QString newHint) {
|
|
||||||
if (m_hint != newHint) {
|
|
||||||
m_hint = newHint;
|
|
||||||
emit hintChanged();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageItem::setDefaultColor(QString color) {
|
|
||||||
if (color != m_color) {
|
|
||||||
m_color = color;
|
|
||||||
emit defaultColorChanged();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ImageItem::setRound(bool value) {
|
|
||||||
if (m_round != value) {
|
|
||||||
m_round = value;
|
|
||||||
emit roundChanged();
|
|
||||||
update();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,52 +0,0 @@
|
||||||
#ifndef IMAGEITEM_H
|
|
||||||
#define IMAGEITEM_H
|
|
||||||
|
|
||||||
#include <QPointer>
|
|
||||||
#include <QImage>
|
|
||||||
#include <QObject>
|
|
||||||
#include <QPainter>
|
|
||||||
#include <QQuickItem>
|
|
||||||
#include <QQuickPaintedItem>
|
|
||||||
|
|
||||||
#include "paintable.h"
|
|
||||||
|
|
||||||
class ImageItem : public QQuickPaintedItem {
|
|
||||||
Q_OBJECT
|
|
||||||
Q_PROPERTY(Paintable* source READ paintable WRITE setPaintable NOTIFY
|
|
||||||
paintableChanged)
|
|
||||||
Q_PROPERTY(QString hint READ hint WRITE setHint NOTIFY hintChanged)
|
|
||||||
Q_PROPERTY(QString defaultColor READ defaultColor WRITE setDefaultColor NOTIFY
|
|
||||||
defaultColorChanged)
|
|
||||||
Q_PROPERTY(bool round READ round WRITE setRound NOTIFY roundChanged)
|
|
||||||
|
|
||||||
public:
|
|
||||||
ImageItem(QQuickItem* parent = nullptr);
|
|
||||||
|
|
||||||
void paint(QPainter* painter);
|
|
||||||
|
|
||||||
Paintable* paintable() { return m_paintable; }
|
|
||||||
void setPaintable(Paintable* paintable);
|
|
||||||
|
|
||||||
QString hint() { return m_hint; }
|
|
||||||
void setHint(QString hint);
|
|
||||||
|
|
||||||
QString defaultColor() { return m_color; }
|
|
||||||
void setDefaultColor(QString color);
|
|
||||||
|
|
||||||
bool round() { return m_round; }
|
|
||||||
void setRound(bool value);
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void paintableChanged();
|
|
||||||
void hintChanged();
|
|
||||||
void defaultColorChanged();
|
|
||||||
void roundChanged();
|
|
||||||
|
|
||||||
private:
|
|
||||||
QPointer<Paintable> m_paintable;
|
|
||||||
QString m_hint = "H";
|
|
||||||
QString m_color;
|
|
||||||
bool m_round = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // IMAGEITEM_H
|
|
|
@ -1,79 +1,89 @@
|
||||||
#include "imageprovider.h"
|
#include "imageprovider.h"
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QMetaObject>
|
|
||||||
#include <QStandardPaths>
|
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtCore/QWaitCondition>
|
#include <QtCore/QThread>
|
||||||
|
|
||||||
#include "jobs/mediathumbnailjob.h"
|
using QMatrixClient::BaseJob;
|
||||||
|
|
||||||
#include "connection.h"
|
ThumbnailResponse::ThumbnailResponse(QMatrixClient::Connection* c,
|
||||||
|
QString id, const QSize& size)
|
||||||
using QMatrixClient::MediaThumbnailJob;
|
: c(c),
|
||||||
|
mediaId(std::move(id)),
|
||||||
ImageProvider::ImageProvider(QObject* parent)
|
requestedSize(size),
|
||||||
: QObject(parent),
|
errorStr("Image request hasn't started") {
|
||||||
QQuickImageProvider(
|
if (requestedSize.isEmpty()) {
|
||||||
QQmlImageProviderBase::Image,
|
errorStr.clear();
|
||||||
QQmlImageProviderBase::ForceAsynchronousImageLoading) {
|
emit finished();
|
||||||
#if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0))
|
return;
|
||||||
qRegisterMetaType<MediaThumbnailJob*>();
|
}
|
||||||
#endif
|
if (mediaId.count('/') != 1) {
|
||||||
|
errorStr =
|
||||||
|
tr("Media id '%1' doesn't follow server/mediaId pattern")
|
||||||
|
.arg(mediaId);
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Execute a request on the main thread asynchronously
|
||||||
|
moveToThread(c->thread());
|
||||||
|
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest,
|
||||||
|
Qt::QueuedConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage ImageProvider::requestImage(const QString& id, QSize* pSize,
|
void ThumbnailResponse::startRequest() {
|
||||||
const QSize& requestedSize) {
|
// Runs in the main thread, not QML thread
|
||||||
if (!id.startsWith("mxc://")) {
|
Q_ASSERT(QThread::currentThread() == c->thread());
|
||||||
qWarning() << "ImageProvider: won't fetch an invalid id:" << id
|
job = c->getThumbnail(mediaId, requestedSize);
|
||||||
<< "doesn't follow server/mediaId pattern";
|
// Connect to any possible outcome including abandonment
|
||||||
return {};
|
// to make sure the QML thread is not left stuck forever.
|
||||||
}
|
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
|
||||||
|
}
|
||||||
|
|
||||||
QUrl mxcUri{id};
|
void ThumbnailResponse::prepareResult() {
|
||||||
|
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||||
QUrl tempfilePath = QUrl::fromLocalFile(
|
Q_ASSERT(job->error() != BaseJob::Pending);
|
||||||
QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/" +
|
|
||||||
mxcUri.fileName() + "-" + QString::number(requestedSize.width()) + "x" +
|
|
||||||
QString::number(requestedSize.height()) + ".png");
|
|
||||||
|
|
||||||
QImage cachedImage;
|
|
||||||
if (cachedImage.load(tempfilePath.toLocalFile())) {
|
|
||||||
if (pSize != nullptr) *pSize = cachedImage.size();
|
|
||||||
return cachedImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
MediaThumbnailJob* job = nullptr;
|
|
||||||
QReadLocker locker(&m_lock);
|
|
||||||
|
|
||||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 10, 0))
|
|
||||||
QMetaObject::invokeMethod(
|
|
||||||
m_connection,
|
|
||||||
[=] { return m_connection->getThumbnail(mxcUri, requestedSize); },
|
|
||||||
Qt::BlockingQueuedConnection, &job);
|
|
||||||
#else
|
|
||||||
QMetaObject::invokeMethod(m_connection, "getThumbnail",
|
|
||||||
Qt::BlockingQueuedConnection,
|
|
||||||
Q_RETURN_ARG(MediaThumbnailJob*, job),
|
|
||||||
Q_ARG(QUrl, mxcUri), Q_ARG(QSize, requestedSize));
|
|
||||||
#endif
|
|
||||||
if (!job) {
|
|
||||||
qDebug() << "ImageProvider: failed to send a request";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
QImage result;
|
|
||||||
{
|
{
|
||||||
QWaitCondition condition; // The most compact way to block on a signal
|
QWriteLocker _(&lock);
|
||||||
job->connect(job, &MediaThumbnailJob::finished, job, [&] {
|
if (job->error() == BaseJob::Success) {
|
||||||
result = job->thumbnail();
|
image = job->thumbnail();
|
||||||
condition.wakeAll();
|
errorStr.clear();
|
||||||
});
|
} else if (job->error() == BaseJob::Abandoned) {
|
||||||
condition.wait(&m_lock);
|
errorStr = tr("Image request has been cancelled");
|
||||||
|
qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
|
||||||
|
} else {
|
||||||
|
errorStr = job->errorString();
|
||||||
|
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-"
|
||||||
|
<< errorStr;
|
||||||
|
}
|
||||||
|
job = nullptr;
|
||||||
}
|
}
|
||||||
|
emit finished();
|
||||||
if (pSize != nullptr) *pSize = result.size();
|
}
|
||||||
|
|
||||||
result.save(tempfilePath.toLocalFile());
|
void ThumbnailResponse::doCancel() {
|
||||||
|
// Runs in the main thread, not QML thread
|
||||||
return result;
|
if (job) {
|
||||||
|
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||||
|
job->abandon();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickTextureFactory* ThumbnailResponse::textureFactory() const {
|
||||||
|
QReadLocker _(&lock);
|
||||||
|
return QQuickTextureFactory::textureFactoryForImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ThumbnailResponse::errorString() const {
|
||||||
|
QReadLocker _(&lock);
|
||||||
|
return errorStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ThumbnailResponse::cancel() {
|
||||||
|
QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel,
|
||||||
|
Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
QQuickImageResponse* ImageProvider::requestImageResponse(
|
||||||
|
const QString& id, const QSize& requestedSize) {
|
||||||
|
qDebug() << "ImageProvider: requesting " << id << "of size" << requestedSize;
|
||||||
|
return new ThumbnailResponse(m_connection.load(), id, requestedSize);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,38 +1,67 @@
|
||||||
#ifndef IMAGEPROVIDER_H
|
#ifndef IMAGEPROVIDER_H
|
||||||
#define IMAGEPROVIDER_H
|
#define IMAGEPROVIDER_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtQuick/QQuickAsyncImageProvider>
|
||||||
|
|
||||||
|
#include <connection.h>
|
||||||
|
#include <jobs/mediathumbnailjob.h>
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QtCore/QReadWriteLock>
|
#include <QtCore/QReadWriteLock>
|
||||||
#include <QtQuick/QQuickImageProvider>
|
#include <QtCore/QAtomicPointer>
|
||||||
|
|
||||||
#include "connection.h"
|
namespace QMatrixClient {
|
||||||
|
class Connection;
|
||||||
|
}
|
||||||
|
|
||||||
class ImageProvider : public QObject, public QQuickImageProvider {
|
class ThumbnailResponse : public QQuickImageResponse {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
ThumbnailResponse(QMatrixClient::Connection* c, QString mediaId,
|
||||||
|
const QSize& requestedSize);
|
||||||
|
~ThumbnailResponse() override = default;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void startRequest();
|
||||||
|
void prepareResult();
|
||||||
|
void doCancel();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QMatrixClient::Connection* c;
|
||||||
|
const QString mediaId;
|
||||||
|
const QSize requestedSize;
|
||||||
|
QMatrixClient::MediaThumbnailJob* job = nullptr;
|
||||||
|
|
||||||
|
QImage image;
|
||||||
|
QString errorStr;
|
||||||
|
mutable QReadWriteLock lock; // Guards ONLY these two members above
|
||||||
|
|
||||||
|
QQuickTextureFactory* textureFactory() const override;
|
||||||
|
QString errorString() const override;
|
||||||
|
void cancel() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ImageProvider : public QObject, public QQuickAsyncImageProvider {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(QMatrixClient::Connection* connection READ connection WRITE
|
Q_PROPERTY(QMatrixClient::Connection* connection READ connection WRITE
|
||||||
setConnection NOTIFY connectionChanged)
|
setConnection NOTIFY connectionChanged)
|
||||||
public:
|
public:
|
||||||
explicit ImageProvider(QObject* parent = nullptr);
|
explicit ImageProvider() = default;
|
||||||
|
|
||||||
QImage requestImage(const QString& id, QSize* pSize,
|
QQuickImageResponse* requestImageResponse(
|
||||||
const QSize& requestedSize) override;
|
const QString& id, const QSize& requestedSize) override;
|
||||||
|
|
||||||
void initializeEngine(QQmlEngine* engine, const char* uri);
|
|
||||||
|
|
||||||
QMatrixClient::Connection* connection() { return m_connection; }
|
QMatrixClient::Connection* connection() { return m_connection; }
|
||||||
void setConnection(QMatrixClient::Connection* newConnection) {
|
void setConnection(QMatrixClient::Connection* connection) {
|
||||||
if (m_connection != newConnection) {
|
m_connection.store(connection);
|
||||||
m_connection = newConnection;
|
emit connectionChanged();
|
||||||
emit connectionChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionChanged();
|
void connectionChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QReadWriteLock m_lock;
|
QAtomicPointer<QMatrixClient::Connection> m_connection;
|
||||||
QMatrixClient::Connection* m_connection = nullptr;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // IMAGEPROVIDER_H
|
#endif // IMAGEPROVIDER_H
|
||||||
|
|
23
src/main.cpp
23
src/main.cpp
|
@ -1,12 +1,13 @@
|
||||||
|
#include <QFontDatabase>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
|
#include <QNetworkProxyFactory>
|
||||||
#include <QQmlApplicationEngine>
|
#include <QQmlApplicationEngine>
|
||||||
#include <QQmlContext>
|
#include <QQmlContext>
|
||||||
|
|
||||||
#include "accountlistmodel.h"
|
#include "accountlistmodel.h"
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
#include "imageitem.h"
|
|
||||||
#include "imageprovider.h"
|
#include "imageprovider.h"
|
||||||
#include "messageeventmodel.h"
|
#include "messageeventmodel.h"
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
@ -23,7 +24,19 @@
|
||||||
using namespace QMatrixClient;
|
using namespace QMatrixClient;
|
||||||
|
|
||||||
int main(int argc, char *argv[]) {
|
int main(int argc, char *argv[]) {
|
||||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
#if defined(Q_OS_LINUX) || defined(Q_OS_WIN) || defined(Q_OS_FREEBSD)
|
||||||
|
if (qgetenv("QT_SCALE_FACTOR").size() == 0) {
|
||||||
|
QSettings settings("ENCOM", "Spectral");
|
||||||
|
float factor = settings.value("Interface/dpi", 100).toFloat() / 100;
|
||||||
|
|
||||||
|
qDebug() << "DPI:" << factor;
|
||||||
|
|
||||||
|
if (factor != -1)
|
||||||
|
qputenv("QT_SCALE_FACTOR", QString::number(factor).toUtf8());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QNetworkProxyFactory::setUseSystemConfiguration(true);
|
||||||
|
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
|
||||||
|
@ -34,7 +47,6 @@ int main(int argc, char *argv[]) {
|
||||||
|
|
||||||
qmlRegisterType<qqsfpm::QQmlSortFilterProxyModel>("SortFilterProxyModel", 0,
|
qmlRegisterType<qqsfpm::QQmlSortFilterProxyModel>("SortFilterProxyModel", 0,
|
||||||
2, "SortFilterProxyModel");
|
2, "SortFilterProxyModel");
|
||||||
qmlRegisterType<ImageItem>("Spectral", 0, 1, "ImageItem");
|
|
||||||
qmlRegisterType<Controller>("Spectral", 0, 1, "Controller");
|
qmlRegisterType<Controller>("Spectral", 0, 1, "Controller");
|
||||||
qmlRegisterType<AccountListModel>("Spectral", 0, 1, "AccountListModel");
|
qmlRegisterType<AccountListModel>("Spectral", 0, 1, "AccountListModel");
|
||||||
qmlRegisterType<RoomListModel>("Spectral", 0, 1, "RoomListModel");
|
qmlRegisterType<RoomListModel>("Spectral", 0, 1, "RoomListModel");
|
||||||
|
@ -51,6 +63,11 @@ int main(int argc, char *argv[]) {
|
||||||
qRegisterMetaType<SpectralRoom *>("SpectralRoom*");
|
qRegisterMetaType<SpectralRoom *>("SpectralRoom*");
|
||||||
qRegisterMetaType<SpectralUser *>("SpectralUser*");
|
qRegisterMetaType<SpectralUser *>("SpectralUser*");
|
||||||
|
|
||||||
|
#if defined(BUNDLE_FONT)
|
||||||
|
QFontDatabase::addApplicationFont(":/assets/font/roboto.ttf");
|
||||||
|
QFontDatabase::addApplicationFont(":/assets/font/twemoji.ttf");
|
||||||
|
#endif
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
engine.addImportPath("qrc:/imports");
|
engine.addImportPath("qrc:/imports");
|
||||||
|
|
|
@ -9,16 +9,11 @@
|
||||||
#include <events/roommemberevent.h>
|
#include <events/roommemberevent.h>
|
||||||
#include <events/simplestateevents.h>
|
#include <events/simplestateevents.h>
|
||||||
|
|
||||||
#include <QRegExp>
|
|
||||||
#include <QtCore/QDebug>
|
#include <QtCore/QDebug>
|
||||||
#include <QtQml> // for qmlRegisterType()
|
#include <QtQml> // for qmlRegisterType()
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
static QString parseAvatarUrl(QUrl url) {
|
|
||||||
return url.host() + "/" + url.path();
|
|
||||||
}
|
|
||||||
|
|
||||||
QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
||||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||||
roles[EventTypeRole] = "eventType";
|
roles[EventTypeRole] = "eventType";
|
||||||
|
@ -39,6 +34,9 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const {
|
||||||
roles[LongOperationRole] = "progressInfo";
|
roles[LongOperationRole] = "progressInfo";
|
||||||
roles[AnnotationRole] = "annotation";
|
roles[AnnotationRole] = "annotation";
|
||||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||||
|
roles[ReplyEventIdRole] = "replyEventId";
|
||||||
|
roles[ReplyAuthorRole] = "replyAuthor";
|
||||||
|
roles[ReplyDisplayRole] = "replyDisplay";
|
||||||
roles[UserMarkerRole] = "userMarker";
|
roles[UserMarkerRole] = "userMarker";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
@ -89,6 +87,9 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
{AboveEventTypeRole, AboveAuthorRole,
|
{AboveEventTypeRole, AboveAuthorRole,
|
||||||
AboveSectionRole, AboveTimeRole});
|
AboveSectionRole, AboveTimeRole});
|
||||||
}
|
}
|
||||||
|
for (auto i = m_currentRoom->maxTimelineIndex() - biggest;
|
||||||
|
i <= m_currentRoom->maxTimelineIndex() - lowest; ++i)
|
||||||
|
refreshLastUserEvents(i);
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this,
|
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this,
|
||||||
[this] { beginInsertRows({}, 0, 0); });
|
[this] { beginInsertRows({}, 0, 0); });
|
||||||
|
@ -108,9 +109,10 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
endMoveRows();
|
endMoveRows();
|
||||||
movingEvent = false;
|
movingEvent = false;
|
||||||
}
|
}
|
||||||
refreshRow(timelineBaseIndex()); // Refresh the looks
|
refreshRow(timelineBaseIndex()); // Refresh the looks
|
||||||
|
refreshLastUserEvents(0);
|
||||||
if (m_currentRoom->timelineSize() > 1) // Refresh above
|
if (m_currentRoom->timelineSize() > 1) // Refresh above
|
||||||
refreshEventRoles(timelineBaseIndex() + 1);
|
refreshEventRoles(timelineBaseIndex() + 1, {ReadMarkerRole});
|
||||||
if (timelineBaseIndex() > 0) // Refresh below, see #312
|
if (timelineBaseIndex() > 0) // Refresh below, see #312
|
||||||
refreshEventRoles(timelineBaseIndex() - 1,
|
refreshEventRoles(timelineBaseIndex() - 1,
|
||||||
{AboveEventTypeRole, AboveAuthorRole,
|
{AboveEventTypeRole, AboveAuthorRole,
|
||||||
|
@ -128,9 +130,11 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
{ReadMarkerRole});
|
{ReadMarkerRole});
|
||||||
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
refreshEventRoles(lastReadEventId, {ReadMarkerRole});
|
||||||
});
|
});
|
||||||
connect(
|
connect(m_currentRoom, &Room::replacedEvent, this,
|
||||||
m_currentRoom, &Room::replacedEvent, this,
|
[this](const RoomEvent *newEvent) {
|
||||||
[this](const RoomEvent *newEvent) { refreshEvent(newEvent->id()); });
|
refreshLastUserEvents(refreshEvent(newEvent->id()) -
|
||||||
|
timelineBaseIndex());
|
||||||
|
});
|
||||||
connect(m_currentRoom, &Room::fileTransferProgress, this,
|
connect(m_currentRoom, &Room::fileTransferProgress, this,
|
||||||
&MessageEventModel::refreshEvent);
|
&MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::fileTransferCompleted, this,
|
connect(m_currentRoom, &Room::fileTransferCompleted, this,
|
||||||
|
@ -140,7 +144,7 @@ void MessageEventModel::setRoom(SpectralRoom *room) {
|
||||||
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
connect(m_currentRoom, &Room::fileTransferCancelled, this,
|
||||||
&MessageEventModel::refreshEvent);
|
&MessageEventModel::refreshEvent);
|
||||||
connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
|
connect(m_currentRoom, &Room::readMarkerForUserMoved, this,
|
||||||
[=](User *user, QString fromEventId, QString toEventId) {
|
[=](User *, QString fromEventId, QString toEventId) {
|
||||||
refreshEventRoles(fromEventId, {UserMarkerRole});
|
refreshEventRoles(fromEventId, {UserMarkerRole});
|
||||||
refreshEventRoles(toEventId, {UserMarkerRole});
|
refreshEventRoles(toEventId, {UserMarkerRole});
|
||||||
});
|
});
|
||||||
|
@ -214,6 +218,23 @@ QString MessageEventModel::renderDate(QDateTime timestamp) const {
|
||||||
return date.toString(Qt::DefaultLocaleShortDate);
|
return date.toString(Qt::DefaultLocaleShortDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageEventModel::refreshLastUserEvents(int baseTimelineRow) {
|
||||||
|
if (!m_currentRoom || m_currentRoom->timelineSize() <= baseTimelineRow)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto &timelineBottom = m_currentRoom->messageEvents().rbegin();
|
||||||
|
const auto &lastSender = (*(timelineBottom + baseTimelineRow))->senderId();
|
||||||
|
const auto limit = timelineBottom + std::min(baseTimelineRow + 10,
|
||||||
|
m_currentRoom->timelineSize());
|
||||||
|
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0);
|
||||||
|
it != limit; ++it) {
|
||||||
|
if ((*it)->senderId() == lastSender) {
|
||||||
|
auto idx = index(it - timelineBottom);
|
||||||
|
emit dataChanged(idx, idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int MessageEventModel::rowCount(const QModelIndex &parent) const {
|
int MessageEventModel::rowCount(const QModelIndex &parent) const {
|
||||||
if (!m_currentRoom || parent.isValid()) return 0;
|
if (!m_currentRoom || parent.isValid()) return 0;
|
||||||
return m_currentRoom->timelineSize();
|
return m_currentRoom->timelineSize();
|
||||||
|
@ -235,13 +256,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
||||||
|
|
||||||
if (role == Qt::DisplayRole) {
|
if (role == Qt::DisplayRole) {
|
||||||
return utils::eventToString(evt, m_currentRoom, Qt::RichText);
|
return utils::removeReply(utils::eventToString(evt, m_currentRoom, Qt::RichText));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == MessageRole) {
|
if (role == MessageRole) {
|
||||||
static const QRegExp rmReplyRegExp("^> <@.*:.*> .*\n\n(.*)");
|
return utils::removeReply(utils::eventToString(evt, m_currentRoom));
|
||||||
return utils::eventToString(evt, m_currentRoom)
|
|
||||||
.replace(rmReplyRegExp, "\\1");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == Qt::ToolTipRole) {
|
if (role == Qt::ToolTipRole) {
|
||||||
|
@ -306,13 +325,14 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
|
|
||||||
if (role == HighlightRole) return m_currentRoom->isEventHighlighted(&evt);
|
if (role == HighlightRole) return m_currentRoom->isEventHighlighted(&evt);
|
||||||
|
|
||||||
if (role == ReadMarkerRole) return evt.id() == lastReadEventId && row > timelineBaseIndex();
|
if (role == ReadMarkerRole)
|
||||||
|
return evt.id() == lastReadEventId && row > timelineBaseIndex();
|
||||||
|
|
||||||
if (role == SpecialMarksRole) {
|
if (role == SpecialMarksRole) {
|
||||||
if (isPending) return pendingIt->deliveryStatus();
|
if (isPending) return pendingIt->deliveryStatus();
|
||||||
|
|
||||||
if (is<RedactionEvent>(evt)) return EventStatus::Hidden;
|
if (is<RedactionEvent>(evt)) return EventStatus::Hidden;
|
||||||
if (evt.isRedacted()) return EventStatus::Redacted;
|
if (evt.isRedacted()) return EventStatus::Hidden;
|
||||||
|
|
||||||
if (evt.isStateEvent() &&
|
if (evt.isStateEvent() &&
|
||||||
static_cast<const StateEventBase &>(evt).repeatsState())
|
static_cast<const StateEventBase &>(evt).repeatsState())
|
||||||
|
@ -348,6 +368,28 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const {
|
||||||
return variantList;
|
return variantList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role == ReplyEventIdRole || role == ReplyDisplayRole ||
|
||||||
|
role == ReplyAuthorRole) {
|
||||||
|
const QString &replyEventId = evt.contentJson()["m.relates_to"]
|
||||||
|
.toObject()["m.in_reply_to"]
|
||||||
|
.toObject()["event_id"]
|
||||||
|
.toString();
|
||||||
|
if (replyEventId.isEmpty()) return {};
|
||||||
|
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||||
|
if (replyIt == m_currentRoom->timelineEdge()) return {};
|
||||||
|
const auto& replyEvt = **replyIt;
|
||||||
|
switch (role) {
|
||||||
|
case ReplyEventIdRole:
|
||||||
|
return replyEventId;
|
||||||
|
case ReplyDisplayRole:
|
||||||
|
return utils::removeReply(utils::eventToString(replyEvt, m_currentRoom, Qt::RichText));
|
||||||
|
case ReplyAuthorRole:
|
||||||
|
return QVariant::fromValue(
|
||||||
|
m_currentRoom->user(replyEvt.senderId()));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
if (role == AboveEventTypeRole || role == AboveSectionRole ||
|
if (role == AboveEventTypeRole || role == AboveSectionRole ||
|
||||||
role == AboveAuthorRole || role == AboveTimeRole)
|
role == AboveAuthorRole || role == AboveTimeRole)
|
||||||
for (auto r = row + 1; r < rowCount(); ++r) {
|
for (auto r = row + 1; r < rowCount(); ++r) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
#ifndef MESSAGEEVENTMODEL_H
|
#ifndef MESSAGEEVENTMODEL_H
|
||||||
#define MESSAGEEVENTMODEL_H
|
#define MESSAGEEVENTMODEL_H
|
||||||
|
|
||||||
#include "spectralroom.h"
|
|
||||||
#include "room.h"
|
#include "room.h"
|
||||||
|
#include "spectralroom.h"
|
||||||
|
|
||||||
#include <QtCore/QAbstractListModel>
|
#include <QtCore/QAbstractListModel>
|
||||||
|
|
||||||
|
@ -30,6 +30,9 @@ class MessageEventModel : public QAbstractListModel {
|
||||||
LongOperationRole,
|
LongOperationRole,
|
||||||
AnnotationRole,
|
AnnotationRole,
|
||||||
UserMarkerRole,
|
UserMarkerRole,
|
||||||
|
ReplyEventIdRole,
|
||||||
|
ReplyAuthorRole,
|
||||||
|
ReplyDisplayRole,
|
||||||
// For debugging
|
// For debugging
|
||||||
EventResolvedTypeRole,
|
EventResolvedTypeRole,
|
||||||
};
|
};
|
||||||
|
@ -62,6 +65,7 @@ class MessageEventModel : public QAbstractListModel {
|
||||||
const QMatrixClient::Room::rev_iter_t& baseIt) const;
|
const QMatrixClient::Room::rev_iter_t& baseIt) const;
|
||||||
QString renderDate(QDateTime timestamp) const;
|
QString renderDate(QDateTime timestamp) const;
|
||||||
|
|
||||||
|
void refreshLastUserEvents(int baseRow);
|
||||||
void refreshEventRoles(int row, const QVector<int>& roles = {});
|
void refreshEventRoles(int row, const QVector<int>& roles = {});
|
||||||
int refreshEventRoles(const QString& eventId, const QVector<int>& roles = {});
|
int refreshEventRoles(const QString& eventId, const QVector<int>& roles = {});
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,7 @@ class NotificationsManager : public QObject {
|
||||||
|
|
||||||
void postNotification(const QString &roomId, const QString &eventId,
|
void postNotification(const QString &roomId, const QString &eventId,
|
||||||
const QString &roomName, const QString &senderName,
|
const QString &roomName, const QString &senderName,
|
||||||
const QString &text, const QImage &icon, const QUrl &iconPath);
|
const QString &text, const QImage &icon);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void notificationClicked(const QString roomId, const QString eventId);
|
void notificationClicked(const QString roomId, const QString eventId);
|
||||||
|
|
|
@ -25,8 +25,7 @@ NotificationsManager::NotificationsManager(QObject *parent)
|
||||||
|
|
||||||
void NotificationsManager::postNotification(
|
void NotificationsManager::postNotification(
|
||||||
const QString &roomid, const QString &eventid, const QString &roomname,
|
const QString &roomid, const QString &eventid, const QString &roomname,
|
||||||
const QString &sender, const QString &text, const QImage &icon,
|
const QString &sender, const QString &text, const QImage &icon) {
|
||||||
const QUrl &iconPath) {
|
|
||||||
uint id = showNotification(roomname, sender + ": " + text, icon);
|
uint id = showNotification(roomname, sender + ": " + text, icon);
|
||||||
notificationIds[id] = roomEventId{roomid, eventid};
|
notificationIds[id] = roomEventId{roomid, eventid};
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,13 +19,11 @@ NotificationsManager::postNotification(
|
||||||
const QString &roomName,
|
const QString &roomName,
|
||||||
const QString &senderName,
|
const QString &senderName,
|
||||||
const QString &text,
|
const QString &text,
|
||||||
const QImage &icon,
|
const QImage &icon)
|
||||||
const QUrl &iconPath)
|
|
||||||
{
|
{
|
||||||
Q_UNUSED(roomId);
|
Q_UNUSED(roomId);
|
||||||
Q_UNUSED(eventId);
|
Q_UNUSED(eventId);
|
||||||
Q_UNUSED(icon);
|
Q_UNUSED(icon);
|
||||||
Q_UNUSED(iconPath);
|
|
||||||
|
|
||||||
NSUserNotification * notif = [[NSUserNotification alloc] init];
|
NSUserNotification * notif = [[NSUserNotification alloc] init];
|
||||||
|
|
||||||
|
|
|
@ -46,8 +46,7 @@ NotificationsManager::NotificationsManager(QObject *parent) : QObject(parent) {}
|
||||||
|
|
||||||
void NotificationsManager::postNotification(
|
void NotificationsManager::postNotification(
|
||||||
const QString &room_id, const QString &event_id, const QString &room_name,
|
const QString &room_id, const QString &event_id, const QString &room_name,
|
||||||
const QString &sender, const QString &text, const QImage &icon,
|
const QString &sender, const QString &text, const QImage &icon) {
|
||||||
const QUrl &iconPath) {
|
|
||||||
Q_UNUSED(room_id)
|
Q_UNUSED(room_id)
|
||||||
Q_UNUSED(event_id)
|
Q_UNUSED(event_id)
|
||||||
Q_UNUSED(icon)
|
Q_UNUSED(icon)
|
||||||
|
@ -65,9 +64,6 @@ void NotificationsManager::postNotification(
|
||||||
templ.setTextField(QString("%1").arg(text).toStdWString(),
|
templ.setTextField(QString("%1").arg(text).toStdWString(),
|
||||||
WinToastTemplate::SecondLine);
|
WinToastTemplate::SecondLine);
|
||||||
|
|
||||||
templ.setImagePath(
|
|
||||||
reinterpret_cast<const wchar_t *>(QDir::toNativeSeparators(iconPath.toLocalFile()).utf16()));
|
|
||||||
|
|
||||||
count++;
|
count++;
|
||||||
CustomHandler *customHandler = new CustomHandler(count, this);
|
CustomHandler *customHandler = new CustomHandler(count, this);
|
||||||
notificationIds[count] = roomEventId{room_id, event_id};
|
notificationIds[count] = roomEventId{room_id, event_id};
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#include "paintable.h"
|
|
||||||
|
|
||||||
Paintable::Paintable(QObject *parent) : QObject(parent) {}
|
|
|
@ -1,20 +0,0 @@
|
||||||
#ifndef PAINTABLE_H
|
|
||||||
#define PAINTABLE_H
|
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
#include <QObject>
|
|
||||||
|
|
||||||
class Paintable : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
Paintable(QObject* parent = nullptr);
|
|
||||||
virtual ~Paintable() = default;
|
|
||||||
|
|
||||||
virtual QImage image(int) = 0;
|
|
||||||
virtual QImage image(int, int) = 0;
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void paintableChanged();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // PAINTABLE_H
|
|
|
@ -70,25 +70,24 @@ void RoomListModel::connectRoomSignals(SpectralRoom* room) {
|
||||||
[=] { unreadMessagesChanged(room); });
|
[=] { unreadMessagesChanged(room); });
|
||||||
connect(room, &Room::notificationCountChanged, this,
|
connect(room, &Room::notificationCountChanged, this,
|
||||||
[=] { unreadMessagesChanged(room); });
|
[=] { unreadMessagesChanged(room); });
|
||||||
|
connect(room, &Room::avatarChanged, this,
|
||||||
|
[this, room] { refresh(room, {AvatarRole}); });
|
||||||
connect(room, &Room::tagsChanged, this, [=] { refresh(room); });
|
connect(room, &Room::tagsChanged, this, [=] { refresh(room); });
|
||||||
connect(room, &Room::joinStateChanged, this, [=] { refresh(room); });
|
connect(room, &Room::joinStateChanged, this, [=] { refresh(room); });
|
||||||
connect(room, &Room::addedMessages, this,
|
connect(room, &Room::addedMessages, this,
|
||||||
[=] { refresh(room, {LastEventRole}); });
|
[=] { refresh(room, {LastEventRole}); });
|
||||||
connect(room, &Room::aboutToAddNewMessages, this,
|
connect(room, &Room::notificationCountChanged, this, [=] {
|
||||||
[=](QMatrixClient::RoomEventsRange eventsRange) {
|
if (room->notificationCount() == 0) return;
|
||||||
RoomEvent* event = (eventsRange.end() - 1)->get();
|
if (room->timelineSize() == 0) return;
|
||||||
User* sender = room->user(event->senderId());
|
const RoomEvent* lastEvent = room->messageEvents().rbegin()->get();
|
||||||
if (sender == room->localUser()) return;
|
if (lastEvent->isStateEvent()) return;
|
||||||
QUrl _url = room->avatarUrl();
|
User* sender = room->user(lastEvent->senderId());
|
||||||
emit newMessage(
|
if (sender == room->localUser()) return;
|
||||||
room->id(), event->id(), room->displayName(),
|
emit newMessage(
|
||||||
sender->displayname(), utils::eventToString(*event),
|
room->id(), lastEvent->id(), room->displayName(),
|
||||||
room->avatar(128),
|
sender->displayname(), utils::eventToString(*lastEvent),
|
||||||
QUrl::fromLocalFile(QStandardPaths::writableLocation(
|
room->avatar(128));
|
||||||
QStandardPaths::CacheLocation) +
|
});
|
||||||
"/avatar/" + _url.authority() + '_' +
|
|
||||||
_url.fileName() + ".png"));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RoomListModel::updateRoom(Room* room, Room* prev) {
|
void RoomListModel::updateRoom(Room* room, Room* prev) {
|
||||||
|
@ -152,7 +151,7 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const {
|
||||||
}
|
}
|
||||||
SpectralRoom* room = m_rooms.at(index.row());
|
SpectralRoom* room = m_rooms.at(index.row());
|
||||||
if (role == NameRole) return room->displayName();
|
if (role == NameRole) return room->displayName();
|
||||||
if (role == PaintableRole) return QVariant::fromValue(room->paintable());
|
if (role == AvatarRole) return room->avatarMediaId();
|
||||||
if (role == TopicRole) return room->topic();
|
if (role == TopicRole) return room->topic();
|
||||||
if (role == CategoryRole) {
|
if (role == CategoryRole) {
|
||||||
if (room->joinState() == JoinState::Invite) return RoomType::Invited;
|
if (room->joinState() == JoinState::Invite) return RoomType::Invited;
|
||||||
|
@ -162,6 +161,7 @@ QVariant RoomListModel::data(const QModelIndex& index, int role) const {
|
||||||
return RoomType::Normal;
|
return RoomType::Normal;
|
||||||
}
|
}
|
||||||
if (role == UnreadCountRole) return room->unreadCount();
|
if (role == UnreadCountRole) return room->unreadCount();
|
||||||
|
if (role == NotificationCountRole) return room->notificationCount();
|
||||||
if (role == HighlightCountRole) return room->highlightCount();
|
if (role == HighlightCountRole) return room->highlightCount();
|
||||||
if (role == LastEventRole) return room->lastEvent();
|
if (role == LastEventRole) return room->lastEvent();
|
||||||
if (role == LastActiveTimeRole) return room->lastActiveTime();
|
if (role == LastActiveTimeRole) return room->lastActiveTime();
|
||||||
|
@ -192,10 +192,11 @@ void RoomListModel::unreadMessagesChanged(SpectralRoom* room) {
|
||||||
QHash<int, QByteArray> RoomListModel::roleNames() const {
|
QHash<int, QByteArray> RoomListModel::roleNames() const {
|
||||||
QHash<int, QByteArray> roles;
|
QHash<int, QByteArray> roles;
|
||||||
roles[NameRole] = "name";
|
roles[NameRole] = "name";
|
||||||
roles[PaintableRole] = "paintable";
|
roles[AvatarRole] = "avatar";
|
||||||
roles[TopicRole] = "topic";
|
roles[TopicRole] = "topic";
|
||||||
roles[CategoryRole] = "category";
|
roles[CategoryRole] = "category";
|
||||||
roles[UnreadCountRole] = "unreadCount";
|
roles[UnreadCountRole] = "unreadCount";
|
||||||
|
roles[NotificationCountRole] = "notificationCount";
|
||||||
roles[HighlightCountRole] = "highlightCount";
|
roles[HighlightCountRole] = "highlightCount";
|
||||||
roles[LastEventRole] = "lastEvent";
|
roles[LastEventRole] = "lastEvent";
|
||||||
roles[LastActiveTimeRole] = "lastActiveTime";
|
roles[LastActiveTimeRole] = "lastActiveTime";
|
||||||
|
|
|
@ -31,10 +31,11 @@ class RoomListModel : public QAbstractListModel {
|
||||||
public:
|
public:
|
||||||
enum EventRoles {
|
enum EventRoles {
|
||||||
NameRole = Qt::UserRole + 1,
|
NameRole = Qt::UserRole + 1,
|
||||||
PaintableRole,
|
AvatarRole,
|
||||||
TopicRole,
|
TopicRole,
|
||||||
CategoryRole,
|
CategoryRole,
|
||||||
UnreadCountRole,
|
UnreadCountRole,
|
||||||
|
NotificationCountRole,
|
||||||
HighlightCountRole,
|
HighlightCountRole,
|
||||||
LastEventRole,
|
LastEventRole,
|
||||||
LastActiveTimeRole,
|
LastActiveTimeRole,
|
||||||
|
@ -76,7 +77,7 @@ class RoomListModel : public QAbstractListModel {
|
||||||
void roomAdded(SpectralRoom* room);
|
void roomAdded(SpectralRoom* room);
|
||||||
void newMessage(const QString& roomId, const QString& eventId,
|
void newMessage(const QString& roomId, const QString& eventId,
|
||||||
const QString& roomName, const QString& senderName,
|
const QString& roomName, const QString& senderName,
|
||||||
const QString& text, const QImage& icon, const QUrl& iconPath);
|
const QString& text, const QImage& icon);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ROOMLISTMODEL_H
|
#endif // ROOMLISTMODEL_H
|
||||||
|
|
|
@ -9,64 +9,67 @@
|
||||||
#include "events/typingevent.h"
|
#include "events/typingevent.h"
|
||||||
|
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QImageReader>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
|
#include "html.h"
|
||||||
|
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
SpectralRoom::SpectralRoom(Connection* connection, QString roomId,
|
SpectralRoom::SpectralRoom(Connection* connection, QString roomId,
|
||||||
JoinState joinState)
|
JoinState joinState)
|
||||||
: Room(connection, std::move(roomId), joinState), m_paintable(this) {
|
: Room(connection, std::move(roomId), joinState) {
|
||||||
connect(this, &SpectralRoom::notificationCountChanged, this,
|
connect(this, &SpectralRoom::notificationCountChanged, this,
|
||||||
&SpectralRoom::countChanged);
|
&SpectralRoom::countChanged);
|
||||||
connect(this, &SpectralRoom::highlightCountChanged, this,
|
connect(this, &SpectralRoom::highlightCountChanged, this,
|
||||||
&SpectralRoom::countChanged);
|
&SpectralRoom::countChanged);
|
||||||
connect(this, &Room::addedMessages, this, [=] { setBusy(false); });
|
connect(this, &Room::addedMessages, this, [=] { setBusy(false); });
|
||||||
|
connect(this, &Room::fileTransferCompleted, this, [=] {
|
||||||
|
setFileUploadingProgress(0);
|
||||||
|
setHasFileUploading(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QString getMIME(const QUrl& fileUrl) {
|
||||||
|
return QMimeDatabase().mimeTypeForFile(fileUrl.toLocalFile()).name();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline QSize getImageSize(const QUrl& imageUrl) {
|
||||||
|
QImageReader reader(imageUrl.toLocalFile());
|
||||||
|
return reader.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectralRoom::chooseAndUploadFile() {
|
void SpectralRoom::chooseAndUploadFile() {
|
||||||
auto localFile = QFileDialog::getOpenFileUrl(Q_NULLPTR, tr("Save File as"));
|
auto localFile = QFileDialog::getOpenFileUrl(Q_NULLPTR, tr("Save File as"));
|
||||||
if (!localFile.isEmpty()) {
|
if (!localFile.isEmpty()) {
|
||||||
UploadContentJob* job =
|
QString txnID = postFile(localFile.fileName(), localFile, false);
|
||||||
connection()->uploadFile(localFile.toLocalFile(), getMIME(localFile));
|
setHasFileUploading(true);
|
||||||
if (isJobRunning(job)) {
|
connect(this, &Room::fileTransferCompleted,
|
||||||
setHasFileUploading(true);
|
[=](QString id, QUrl localFile, QUrl mxcUrl) {
|
||||||
connect(job, &BaseJob::uploadProgress, this,
|
if (id == txnID) {
|
||||||
[=](qint64 bytesSent, qint64 bytesTotal) {
|
setFileUploadingProgress(0);
|
||||||
if (bytesTotal != 0) {
|
setHasFileUploading(false);
|
||||||
setFileUploadingProgress(bytesSent * 100 / bytesTotal);
|
}
|
||||||
}
|
});
|
||||||
});
|
connect(this, &Room::fileTransferFailed, [=](QString id, QString error) {
|
||||||
connect(job, &BaseJob::success, this,
|
if (id == txnID) {
|
||||||
[=] { postFile(localFile, job->contentUri()); });
|
|
||||||
connect(job, &BaseJob::finished, this, [=] {
|
|
||||||
setHasFileUploading(false);
|
|
||||||
setFileUploadingProgress(0);
|
setFileUploadingProgress(0);
|
||||||
});
|
setHasFileUploading(false);
|
||||||
} else {
|
}
|
||||||
qDebug() << "Failed transfer.";
|
});
|
||||||
}
|
connect(
|
||||||
|
this, &Room::fileTransferProgress,
|
||||||
|
[=](QString id, qint64 progress, qint64 total) {
|
||||||
|
if (id == txnID) {
|
||||||
|
qDebug() << "Progress:" << progress << total;
|
||||||
|
setFileUploadingProgress(int(float(progress) / float(total) * 100));
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectralRoom::postFile(const QUrl& localFile, const QUrl& mxcUrl) {
|
|
||||||
const QString mime = getMIME(localFile);
|
|
||||||
const QString fileName = localFile.fileName();
|
|
||||||
QString msgType = "m.file";
|
|
||||||
if (mime.startsWith("image")) msgType = "m.image";
|
|
||||||
if (mime.startsWith("video")) msgType = "m.video";
|
|
||||||
if (mime.startsWith("audio")) msgType = "m.audio";
|
|
||||||
QJsonObject json{QJsonObject{{"msgtype", msgType},
|
|
||||||
{"body", fileName},
|
|
||||||
{"filename", fileName},
|
|
||||||
{"url", mxcUrl.url()}}};
|
|
||||||
postJson("m.room.message", json);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SpectralRoom::getMIME(const QUrl& fileUrl) const {
|
|
||||||
return QMimeDatabase().mimeTypeForFile(fileUrl.toLocalFile()).name();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SpectralRoom::saveFileAs(QString eventId) {
|
void SpectralRoom::saveFileAs(QString eventId) {
|
||||||
auto fileName = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Save File as"),
|
auto fileName = QFileDialog::getSaveFileName(Q_NULLPTR, tr("Save File as"),
|
||||||
fileNameToDownload(eventId));
|
fileNameToDownload(eventId));
|
||||||
|
@ -85,16 +88,14 @@ bool SpectralRoom::hasUsersTyping() {
|
||||||
return count != 0;
|
return count != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString SpectralRoom::getUsersTyping() {
|
QVariantList SpectralRoom::getUsersTyping() {
|
||||||
QString usersTypingStr;
|
|
||||||
QList<User*> users = usersTyping();
|
QList<User*> users = usersTyping();
|
||||||
users.removeOne(localUser());
|
users.removeOne(localUser());
|
||||||
|
QVariantList out;
|
||||||
for (User* user : users) {
|
for (User* user : users) {
|
||||||
usersTypingStr += user->displayname() + " ";
|
out.append(QVariant::fromValue(user));
|
||||||
}
|
}
|
||||||
usersTypingStr += users.count() < 2 ? "is" : "are";
|
return out;
|
||||||
usersTypingStr += " typing.";
|
|
||||||
return usersTypingStr;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SpectralRoom::sendTypingNotification(bool isTyping) {
|
void SpectralRoom::sendTypingNotification(bool isTyping) {
|
||||||
|
@ -162,8 +163,6 @@ QDateTime SpectralRoom::lastActiveTime() {
|
||||||
return messageEvents().rbegin()->get()->timestamp();
|
return messageEvents().rbegin()->get()->timestamp();
|
||||||
}
|
}
|
||||||
|
|
||||||
float SpectralRoom::orderForTag(QString name) { return tag(name).order; }
|
|
||||||
|
|
||||||
int SpectralRoom::savedTopVisibleIndex() const {
|
int SpectralRoom::savedTopVisibleIndex() const {
|
||||||
return firstDisplayedMarker() == timelineEdge()
|
return firstDisplayedMarker() == timelineEdge()
|
||||||
? 0
|
? 0
|
||||||
|
@ -204,3 +203,22 @@ QVariantList SpectralRoom::getUsers(const QString& prefix) {
|
||||||
|
|
||||||
return matchedList;
|
return matchedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString SpectralRoom::postMarkdownText(const QString& markdown) {
|
||||||
|
unsigned char *sequence = (unsigned char *) qstrdup(markdown.toUtf8().constData());
|
||||||
|
qint64 length = strlen((char *) sequence);
|
||||||
|
|
||||||
|
hoedown_renderer* renderer = hoedown_html_renderer_new(HOEDOWN_HTML_USE_XHTML, 32);
|
||||||
|
hoedown_extensions extensions = (hoedown_extensions) ((HOEDOWN_EXT_BLOCK | HOEDOWN_EXT_SPAN | HOEDOWN_EXT_MATH_EXPLICIT) & ~HOEDOWN_EXT_QUOTE);
|
||||||
|
hoedown_document* document = hoedown_document_new(renderer, extensions, 32);
|
||||||
|
hoedown_buffer* html = hoedown_buffer_new(length);
|
||||||
|
hoedown_document_render(document, html, sequence, length);
|
||||||
|
QString result = QString::fromUtf8((char *) html->data, html->size);
|
||||||
|
|
||||||
|
free(sequence);
|
||||||
|
hoedown_buffer_free(html);
|
||||||
|
hoedown_document_free(document);
|
||||||
|
hoedown_html_renderer_free(renderer);
|
||||||
|
|
||||||
|
return postHtmlText(markdown, result);
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue