Merge branch 'material' into 'develop'
Material Design See merge request b0/spectral!30
This commit is contained in:
commit
70de1e3d7c
|
@ -22,7 +22,7 @@ build_script:
|
|||
|
||||
after_build:
|
||||
- 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%\"
|
||||
|
||||
artifacts:
|
||||
|
|
|
@ -22,11 +22,11 @@ build-appimage:
|
|||
before_script:
|
||||
- git submodule update --init --recursive
|
||||
script:
|
||||
- /opt/qt511/bin/qt511-env.sh
|
||||
- /opt/qt511/bin/qmake CONFIG+=debug CONFIG+=qml_debug PREFIX=/usr
|
||||
- /opt/qt512/bin/qt512-env.sh
|
||||
- /opt/qt512/bin/qmake CONFIG+=debug CONFIG+=qml_debug PREFIX=/usr
|
||||
- make
|
||||
- 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:
|
||||
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
|
||||
runtime: org.kde.Platform
|
||||
runtime-version: 5.11
|
||||
runtime-version: 5.12
|
||||
sdk: org.kde.Sdk
|
||||
command: spectral
|
||||
finish-args:
|
||||
|
|
|
@ -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 {
|
||||
ScrollHelper {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick 2.12
|
||||
|
||||
import Spectral.Setting 0.1
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
TextField {
|
||||
selectByMouse: true
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
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: realSource
|
||||
sourceSize.width: width
|
||||
sourceSize.height: width
|
||||
fillMode: Image.PreserveAspectCrop
|
||||
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.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
|
||||
import Spectral.Component 2.0
|
||||
|
||||
import Spectral 0.1
|
||||
import Spectral.Setting 0.1
|
||||
|
||||
Popup {
|
||||
property var emojiModel
|
||||
property var textArea
|
||||
ColumnLayout {
|
||||
property string emojiCategory: "people"
|
||||
property var textArea
|
||||
property var emojiModel
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
GridView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
ListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 48
|
||||
Layout.leftMargin: 24
|
||||
Layout.rightMargin: 24
|
||||
|
||||
cellWidth: 36
|
||||
cellHeight: 36
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
|
||||
boundsBehavior: Flickable.DragOverBounds
|
||||
clip: true
|
||||
|
||||
clip: true
|
||||
orientation: ListView.Horizontal
|
||||
|
||||
model: emojiModel.model[emojiCategory]
|
||||
|
||||
delegate: ItemDelegate {
|
||||
width: 36
|
||||
height: 36
|
||||
|
||||
contentItem: Text {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
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 {}
|
||||
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" }
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 2
|
||||
delegate: ItemDelegate {
|
||||
width: 64
|
||||
height: 48
|
||||
|
||||
color: Material.accent
|
||||
}
|
||||
contentItem: Label {
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
||||
Row {
|
||||
Repeater {
|
||||
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
|
||||
}
|
||||
font.pixelSize: 24
|
||||
text: label
|
||||
}
|
||||
|
||||
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.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import Spectral.Setting 0.1
|
||||
import Spectral.Font 0.1
|
||||
|
@ -10,8 +10,8 @@ Text {
|
|||
|
||||
id: materialLabel
|
||||
|
||||
color: MSettings.darkTheme ? "white" : "dark"
|
||||
font.pointSize: 16
|
||||
color: MPalette.foreground
|
||||
font.pixelSize: 24
|
||||
font.family: MaterialFont.name
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
|
||||
MouseArea {
|
||||
id: root
|
||||
|
@ -66,14 +66,13 @@ MouseArea {
|
|||
}
|
||||
|
||||
onWheel: {
|
||||
var newPos = calculateNewPosition(flickable, wheel);
|
||||
// console.warn("Delta: ", wheel.pixelDelta.y);
|
||||
// console.warn("Old position: ", flickable.contentY);
|
||||
// console.warn("New position: ", newPos);
|
||||
|
||||
// Show the scrollbars
|
||||
flickable.flick(0, 0);
|
||||
flickable.contentY = newPos;
|
||||
flickable.contentY = calculateNewPosition(flickable, wheel);
|
||||
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.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Window 2.1
|
||||
import Spectral.Setting 0.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
@ -64,7 +65,7 @@ Item {
|
|||
property Component handleDelegate: Rectangle {
|
||||
width: 1
|
||||
height: 1
|
||||
visible: false
|
||||
color: MSettings.darkTheme ? "#424242" : "#E1E1E1"
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -88,7 +89,7 @@ Item {
|
|||
|
||||
/*! \qmlmethod void SplitView::addItem(Item item)
|
||||
Add an item to the end of the view.
|
||||
\since QtQuick.Controls 1.3 */
|
||||
\since QtQuick.Controls 1.12 */
|
||||
function addItem(item) {
|
||||
d.updateLayoutGuard = true
|
||||
d.addItem_impl(item)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import Qt.labs.platform 1.0
|
||||
|
||||
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,158 @@
|
|||
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
|
||||
}
|
||||
}
|
||||
|
||||
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.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
|
||||
import Spectral 0.1
|
||||
import Spectral.Setting 0.1
|
||||
|
||||
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 highlighted: !(sentByMe || eventType === "notice" )
|
||||
readonly property bool sentByMe: author === currentRoom.localUser
|
||||
readonly property bool isText: eventType === "notice" || eventType === "message"
|
||||
|
||||
signal saveFileAs()
|
||||
signal openExternally()
|
||||
|
||||
z: -5
|
||||
|
||||
id: messageRow
|
||||
|
||||
Layout.alignment: sentByMe ? Qt.AlignRight : Qt.AlignLeft
|
||||
|
||||
spacing: 6
|
||||
id: root
|
||||
|
||||
ImageItem {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignTop
|
||||
spacing: 0
|
||||
|
||||
Label {
|
||||
Layout.leftMargin: 48
|
||||
|
||||
text: author.displayName
|
||||
|
||||
round: false
|
||||
visible: avatarVisible
|
||||
hint: author.displayName
|
||||
source: author.paintable
|
||||
|
||||
font.pixelSize: 13
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredWidth: 40
|
||||
Layout.preferredHeight: 40
|
||||
Layout.alignment: Qt.AlignTop
|
||||
RowLayout {
|
||||
z: -5
|
||||
|
||||
color: "transparent"
|
||||
visible: !(sentByMe || avatarVisible)
|
||||
}
|
||||
id: messageRow
|
||||
|
||||
GenericBubble {
|
||||
Layout.maximumWidth: messageListView.width - (!sentByMe ? 40 + messageRow.spacing : 0)
|
||||
spacing: 4
|
||||
|
||||
id: genericBubble
|
||||
Avatar {
|
||||
Layout.preferredWidth: 32
|
||||
Layout.preferredHeight: 32
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
highlighted: messageRow.highlighted
|
||||
colored: highlighted && (eventType === "notice" || highlight)
|
||||
visible: avatarVisible
|
||||
hint: author.displayName
|
||||
source: author.avatarMediaId
|
||||
}
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
id: messageColumn
|
||||
Label {
|
||||
Layout.preferredWidth: 32
|
||||
Layout.preferredHeight: 32
|
||||
Layout.alignment: Qt.AlignTop
|
||||
|
||||
spacing: 0
|
||||
visible: !(sentByMe || avatarVisible)
|
||||
|
||||
TimelineLabel {
|
||||
Layout.fillWidth: true
|
||||
text: Qt.formatDateTime(time, "hh:mm")
|
||||
color: MPalette.lighter
|
||||
|
||||
id: authorLabel
|
||||
font.pixelSize: 10
|
||||
horizontalAlignment: Label.AlignHCenter
|
||||
verticalAlignment: Label.AlignVCenter
|
||||
}
|
||||
|
||||
visible: messageRow.avatarVisible
|
||||
text: author.displayName
|
||||
Material.foreground: Material.accent
|
||||
coloredBackground: highlighted
|
||||
font.bold: true
|
||||
Control {
|
||||
Layout.maximumWidth: messageListView.width - (!sentByMe ? 32 + messageRow.spacing : 0) - 48
|
||||
|
||||
MouseArea {
|
||||
verticalPadding: 8
|
||||
horizontalPadding: 16
|
||||
|
||||
background: Rectangle {
|
||||
color: sentByMe ? "#009DC2" : eventType === "notice" ? "#4285F4" : "#673AB7"
|
||||
radius: 18
|
||||
|
||||
AutoMouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: roomPanelInput.insert(author.displayName)
|
||||
}
|
||||
}
|
||||
|
||||
TextEdit {
|
||||
Layout.fillWidth: true
|
||||
id: messageMouseArea
|
||||
|
||||
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
|
||||
color: highlighted ? "white": Material.foreground
|
||||
id: messageContextMenu
|
||||
|
||||
font.family: authorLabel.font.family
|
||||
font.pointSize: 10
|
||||
selectByMouse: true
|
||||
readOnly: true
|
||||
wrapMode: Label.Wrap
|
||||
selectedTextColor: highlighted ? Material.accent : "white"
|
||||
selectionColor: highlighted ? "white" : Material.accent
|
||||
textFormat: Text.RichText
|
||||
MenuItem {
|
||||
text: "View Source"
|
||||
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
onTriggered: {
|
||||
sourceDialog.sourceText = toolTip
|
||||
sourceDialog.open()
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
text: "Reply"
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
onTriggered: {
|
||||
roomPanelInput.replyUser = author
|
||||
roomPanelInput.replyEventID = eventId
|
||||
roomPanelInput.replyContent = messageContextMenu.selectedText || message
|
||||
roomPanelInput.isReply = true
|
||||
roomPanelInput.focus()
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
text: "Redact"
|
||||
|
||||
Loader {
|
||||
sourceComponent: {
|
||||
switch (eventType) {
|
||||
case "image":
|
||||
return imageComponent
|
||||
case "file":
|
||||
return fileComponent
|
||||
case "audio":
|
||||
return audioComponent
|
||||
onTriggered: currentRoom.redactEvent(eventId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
active: eventType === "image" || eventType === "file" || eventType === "audio"
|
||||
}
|
||||
|
||||
Row {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
contentItem: ColumnLayout {
|
||||
Control {
|
||||
Layout.fillWidth: true
|
||||
|
||||
spacing: 4
|
||||
visible: replyEventId || ""
|
||||
|
||||
TimelineLabel {
|
||||
visible: userMarker.length > 5
|
||||
text: userMarker.length - 5 + "+"
|
||||
coloredBackground: highlighted
|
||||
Material.foreground: "grey"
|
||||
font.pointSize: 8
|
||||
}
|
||||
padding: 8
|
||||
|
||||
Repeater {
|
||||
model: userMarker.length > 5 ? userMarker.slice(0, 5) : userMarker
|
||||
|
||||
ImageItem {
|
||||
width: parent.height
|
||||
height: parent.height
|
||||
|
||||
hint: modelData.displayName
|
||||
source: modelData.paintable
|
||||
background: Item {
|
||||
Rectangle {
|
||||
anchors.leftMargin: 0
|
||||
width: 2
|
||||
height: parent.height
|
||||
|
||||
color: "white"
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: goToEvent(replyEventId)
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
readMarkerDialog.listModel = userMarker
|
||||
readMarkerDialog.open()
|
||||
contentItem: RowLayout {
|
||||
spacing: 8
|
||||
|
||||
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 {
|
||||
id: timeLabel
|
||||
TextEdit {
|
||||
Layout.fillWidth: true
|
||||
|
||||
visible: Math.abs(time - aboveTime) > 600000 || index == 0
|
||||
text: Qt.formatTime(time, "hh:mm")
|
||||
coloredBackground: highlighted
|
||||
Material.foreground: "grey"
|
||||
font.pointSize: 8
|
||||
}
|
||||
}
|
||||
}
|
||||
id: contentLabel
|
||||
|
||||
Component {
|
||||
id: imageComponent
|
||||
text: "<style>a{color: white;} .user-pill{}</style>" + display
|
||||
|
||||
DownloadableContent {
|
||||
width: messageImage.width
|
||||
height: messageImage.height
|
||||
color: "white"
|
||||
|
||||
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 {
|
||||
z: -4
|
||||
|
||||
id: messageImage
|
||||
|
||||
sourceSize: 128
|
||||
source: "image://mxc/" + (content.thumbnail_url ? content.thumbnail_url : content.url)
|
||||
|
||||
onClicked: downloadAndOpen()
|
||||
}
|
||||
|
||||
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")
|
||||
onLinkActivated: {
|
||||
if (link.startsWith("https://matrix.to/")) {
|
||||
var result = link.replace(/\?.*/, "").match("https://matrix.to/#/(!.*:.*)/(\\$.*:.*)")
|
||||
if (result.length < 3) return
|
||||
if (result[1] != currentRoom.id) return
|
||||
if (!result[2]) return
|
||||
goToEvent(result[2])
|
||||
} else {
|
||||
Qt.openUrlExternally(link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: DownloadableContent {
|
||||
id: downloadable
|
||||
|
||||
onDownloadedChanged: downloaded && playOnFinished ? spectralController.playAudio(progressInfo.localPath) : {}
|
||||
|
||||
Component.onCompleted: {
|
||||
messageRow.saveFileAs.connect(saveFileAs)
|
||||
messageRow.openExternally.connect(downloadAndOpen)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
acceptedButtons: Qt.NoButton
|
||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
|
||||
import Spectral.Setting 0.1
|
||||
|
||||
Label {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
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
|
||||
linkColor: "white"
|
||||
textFormat: MSettings.richText ? Text.RichText : Text.StyledText
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
|
||||
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
|
||||
MessageDelegate 2.0 MessageDelegate.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
|
||||
AutoTextField 2.0 AutoTextField.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
|
||||
|
||||
/*!
|
||||
|
@ -7,7 +7,8 @@ import QtGraphicalEffects 1.0
|
|||
Item {
|
||||
id: effect
|
||||
|
||||
property variant source
|
||||
property var source
|
||||
readonly property Item sourceItem: source.sourceItem
|
||||
|
||||
property int elevation: 0
|
||||
|
||||
|
@ -134,7 +135,7 @@ Item {
|
|||
glowRadius: modelData.blur/2
|
||||
spread: 0.05
|
||||
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
|
||||
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
|
||||
import QtQuick 2.9
|
||||
import QtQuick 2.12
|
||||
|
||||
FontLoader {
|
||||
source: "qrc:/assets/font/material.ttf"
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Spectral.Font
|
||||
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.Controls 2.2
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
|
||||
import Spectral.Component 2.0
|
||||
|
||||
import Spectral 0.1
|
||||
|
||||
import "qrc:/js/util.js" as Util
|
||||
|
||||
Drawer {
|
||||
property var room
|
||||
|
||||
id: roomDrawer
|
||||
|
||||
edge: Qt.RightEdge
|
||||
interactive: false
|
||||
|
||||
ToolButton {
|
||||
contentItem: MaterialIcon { icon: "\ue5c4" }
|
||||
|
||||
onClicked: roomDrawer.close()
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 32
|
||||
|
||||
ImageItem {
|
||||
Avatar {
|
||||
Layout.preferredWidth: 96
|
||||
Layout.preferredHeight: 96
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
hint: room ? room.displayName : "No name"
|
||||
source: room ? room.paintable : null
|
||||
source: room ? room.avatarMediaId : null
|
||||
}
|
||||
|
||||
Label {
|
||||
|
@ -57,7 +48,7 @@ Drawer {
|
|||
|
||||
wrapMode: Label.Wrap
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: room ? room.memberCount + " Members" : "No Member Count"
|
||||
text: room ? room.totalMemberCount + " Members" : "No Member Count"
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
@ -124,11 +115,11 @@ Drawer {
|
|||
anchors.margins: 8
|
||||
spacing: 12
|
||||
|
||||
ImageItem {
|
||||
Avatar {
|
||||
Layout.preferredWidth: height
|
||||
Layout.fillHeight: true
|
||||
|
||||
source: paintable
|
||||
source: avatar
|
||||
hint: name
|
||||
}
|
||||
|
||||
|
|
|
@ -1,80 +1,81 @@
|
|||
import QtQuick 2.9
|
||||
import QtQuick.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
|
||||
import Spectral 0.1
|
||||
import Spectral.Effect 2.0
|
||||
import Spectral.Component 2.0
|
||||
import Spectral.Setting 0.1
|
||||
|
||||
Rectangle {
|
||||
property alias paintable: headerImage.source
|
||||
Control {
|
||||
property alias avatar: headerImage.source
|
||||
property alias topic: headerTopicLabel.text
|
||||
property bool atTop: false
|
||||
signal clicked()
|
||||
|
||||
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.margins: 12
|
||||
|
||||
id: roomHeader
|
||||
spacing: 12
|
||||
|
||||
onClicked: header.clicked()
|
||||
Avatar {
|
||||
Layout.preferredWidth: height
|
||||
Layout.fillHeight: true
|
||||
|
||||
RowLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: 12
|
||||
id: headerImage
|
||||
|
||||
spacing: 12
|
||||
source: currentRoom.avatarMediaId
|
||||
hint: currentRoom ? currentRoom.displayName : "No name"
|
||||
}
|
||||
|
||||
ImageItem {
|
||||
Layout.preferredWidth: height
|
||||
Layout.fillHeight: true
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
id: headerImage
|
||||
visible: parent.width > 64
|
||||
|
||||
source: currentRoom.paintable
|
||||
hint: currentRoom ? currentRoom.displayName : "No name"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
Layout.fillWidth: 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 {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
text: currentRoom ? currentRoom.displayName : ""
|
||||
color: "white"
|
||||
font.pointSize: 12
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
id: headerTopicLabel
|
||||
|
||||
Label {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
id: headerTopicLabel
|
||||
|
||||
color: "white"
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
color: MPalette.lighter
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
width: parent.width
|
||||
z: 10
|
||||
anchors.bottom: parent.bottom
|
||||
RippleEffect {
|
||||
anchors.fill: parent
|
||||
|
||||
Material.accent: "white"
|
||||
visible: currentRoom && currentRoom.busy
|
||||
indeterminate: true
|
||||
onClicked: header.clicked()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.Controls 2.2
|
||||
import QtQuick 2.12
|
||||
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
|
||||
|
||||
RoomListPanelForm {
|
||||
model: sortedRoomListModel
|
||||
Item {
|
||||
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 {
|
||||
id: sortedRoomListModel
|
||||
|
||||
sourceModel: listModel
|
||||
sourceModel: roomListModel
|
||||
|
||||
proxyRoles: ExpressionRole {
|
||||
name: "display"
|
||||
|
@ -53,9 +80,787 @@ RoomListPanelForm {
|
|||
]
|
||||
}
|
||||
|
||||
Shortcut {
|
||||
sequence: StandardKey.Find
|
||||
onActivated: searchField.forceActiveFocus()
|
||||
Drawer {
|
||||
width: Math.max(root.width, 400)
|
||||
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
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
|
||||
id: searchField
|
||||
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
placeholderText: "Search..."
|
||||
color: MPalette.lighter
|
||||
|
||||
background: Item {}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -70,11 +875,30 @@ RoomListPanelForm {
|
|||
|
||||
title: "Action Required"
|
||||
modal: true
|
||||
standardButtons: Dialog.Ok | Dialog.Cancel
|
||||
|
||||
contentItem: Label { text: "Accept this invitation?" }
|
||||
|
||||
onAccepted: currentRoom.acceptInvitation()
|
||||
onRejected: currentRoom.forget()
|
||||
footer: DialogButtonBox {
|
||||
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,426 @@
|
|||
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 {
|
||||
roomHeader.paintable: currentRoom ? currentRoom.paintable : null
|
||||
roomHeader.topic: currentRoom ? (currentRoom.topic).replace(/(\r\n\t|\n|\r\t)/gm,"") : ""
|
||||
roomHeader.onClicked: roomDrawer.open()
|
||||
import Spectral.Component 2.0
|
||||
import Spectral.Component.Emoji 2.0
|
||||
import Spectral.Component.Timeline 2.0
|
||||
import Spectral.Effect 2.0
|
||||
|
||||
sortedMessageEventModel.onModelReset: {
|
||||
if (currentRoom) {
|
||||
var lastScrollPosition = sortedMessageEventModel.mapFromSource(currentRoom.savedTopVisibleIndex())
|
||||
messageListView.currentIndex = lastScrollPosition
|
||||
if (messageListView.contentY < messageListView.originY + 10 || currentRoom.timelineSize < 20)
|
||||
currentRoom.getPreviousContent(100)
|
||||
}
|
||||
import Spectral 0.1
|
||||
import Spectral.Setting 0.1
|
||||
import SortFilterProxyModel 0.2
|
||||
|
||||
Item {
|
||||
property var currentRoom: null
|
||||
|
||||
id: root
|
||||
|
||||
MessageEventModel {
|
||||
id: messageEventModel
|
||||
room: currentRoom
|
||||
}
|
||||
|
||||
messageListView {
|
||||
property int largestVisibleIndex: messageListView.count > 0 ? messageListView.indexAt(messageListView.contentX, messageListView.contentY + messageListView.height - 1) : -1
|
||||
RoomDrawer {
|
||||
width: Math.min(root.width * 0.7, 480)
|
||||
height: root.height
|
||||
|
||||
onContentYChanged: {
|
||||
if(currentRoom && messageListView.contentY - 5000 < messageListView.originY)
|
||||
currentRoom.getPreviousContent(50);
|
||||
id: roomDrawer
|
||||
|
||||
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
|
||||
|
||||
displaced: Transition {
|
||||
NumberAnimation {
|
||||
property: "y"; duration: 200
|
||||
easing.type: Easing.OutQuad
|
||||
spacing: 16
|
||||
|
||||
AutoListView {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
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
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
|
||||
id: roomPanelInput
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
goBottomFab.onClicked: goToEvent(currentRoom.readMarkerEventId)
|
||||
goTopFab.onClicked: messageListView.positionViewAtBeginning()
|
||||
|
||||
function goToEvent(eventID) {
|
||||
var index = messageEventModel.eventIDToIndex(eventID)
|
||||
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) {
|
||||
var readMarker = sortedMessageEventModel.get(messageListView.largestVisibleIndex).eventId
|
||||
if (!readMarker) return
|
||||
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.Controls 2.2
|
||||
import QtQuick.Layouts 1.3
|
||||
import QtQuick.Controls.Material 2.2
|
||||
import QtQuick 2.12
|
||||
import QtQuick.Controls 2.12
|
||||
import QtQuick.Layouts 1.12
|
||||
import QtQuick.Controls.Material 2.12
|
||||
|
||||
import Spectral.Component 2.0
|
||||
import Spectral.Component.Emoji 2.0
|
||||
|
@ -10,167 +10,222 @@ import Spectral.Setting 0.1
|
|||
|
||||
import Spectral 0.1
|
||||
|
||||
import "qrc:/js/md.js" as Markdown
|
||||
|
||||
Rectangle {
|
||||
property bool isReply
|
||||
property string replyUserID
|
||||
Control {
|
||||
property alias isReply: replyItem.visible
|
||||
property var replyUser
|
||||
property string replyEventID
|
||||
property string replyContent
|
||||
|
||||
property bool isAutoCompleting
|
||||
property alias isAutoCompleting: autoCompleteListView.visible
|
||||
property var autoCompleteModel
|
||||
property int autoCompleteBeginPosition
|
||||
property int autoCompleteEndPosition
|
||||
|
||||
color: MSettings.darkTheme ? "#303030" : "#fafafa"
|
||||
id: root
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: ElevationEffect {
|
||||
elevation: 2
|
||||
padding: 0
|
||||
|
||||
background: Rectangle {
|
||||
color: MSettings.darkTheme ? "#303030" : "#fafafa"
|
||||
radius: 24
|
||||
|
||||
layer.enabled: true
|
||||
layer.effect: ElevationEffect {
|
||||
elevation: 2
|
||||
}
|
||||
}
|
||||
|
||||
Popup {
|
||||
x: 0
|
||||
y: -height - 10
|
||||
width: Math.min(autoCompleteListView.contentWidth, parent.width)
|
||||
height: 36
|
||||
padding: 0
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 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
|
||||
|
||||
visible: false
|
||||
|
||||
model: autoCompleteModel
|
||||
|
||||
clip: true
|
||||
spacing: 4
|
||||
orientation: ListView.Horizontal
|
||||
highlightFollowsCurrentItem: true
|
||||
keyNavigationWraps: true
|
||||
|
||||
highlight: Rectangle {
|
||||
color: Material.accent
|
||||
opacity: 0.4
|
||||
}
|
||||
|
||||
delegate: ItemDelegate {
|
||||
delegate: Control {
|
||||
property string autoCompleteText: modelData.displayName || modelData.unicode
|
||||
property bool isEmoji: modelData.unicode != null
|
||||
readonly property bool highlighted: autoCompleteListView.currentIndex === index
|
||||
|
||||
height: parent.height
|
||||
padding: 4
|
||||
height: 36
|
||||
padding: 8
|
||||
|
||||
background: Rectangle {
|
||||
visible: !isEmoji
|
||||
color: highlighted ? Material.accent : "transparent"
|
||||
border.color: Material.accent
|
||||
border.width: 2
|
||||
radius: height / 2
|
||||
}
|
||||
|
||||
contentItem: Row {
|
||||
spacing: 8
|
||||
spacing: 4
|
||||
|
||||
Text {
|
||||
width: parent.height
|
||||
height: parent.height
|
||||
width: 20
|
||||
height: 20
|
||||
visible: isEmoji
|
||||
text: autoCompleteText
|
||||
font.pointSize: 16
|
||||
font.pixelSize: 24
|
||||
font.family: "Emoji"
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
}
|
||||
ImageItem {
|
||||
width: parent.height
|
||||
height: parent.height
|
||||
Avatar {
|
||||
width: 20
|
||||
height: 20
|
||||
visible: !isEmoji
|
||||
source: modelData.paintable || null
|
||||
source: modelData.avatarMediaId || null
|
||||
}
|
||||
Label {
|
||||
height: parent.height
|
||||
visible: !isEmoji
|
||||
text: autoCompleteText
|
||||
color: highlighted ? "white" : Material.accent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
autoCompleteListView.currentIndex = index
|
||||
inputField.replaceAutoComplete(autoCompleteText)
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
autoCompleteListView.currentIndex = index
|
||||
inputField.replaceAutoComplete(autoCompleteText)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
Rectangle {
|
||||
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 {
|
||||
property real progress: 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumHeight: 48
|
||||
|
||||
id: inputField
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
placeholderText: isReply ? "Reply to " + replyUserID : "Send a Message"
|
||||
leftPadding: 16
|
||||
placeholderText: "Send a Message"
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
selectByMouse: true
|
||||
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 {
|
||||
|
@ -193,19 +248,18 @@ Rectangle {
|
|||
onTriggered: currentRoom.sendTypingNotification(true)
|
||||
}
|
||||
|
||||
ToolTip.visible: currentRoom
|
||||
&& currentRoom.hasUsersTyping
|
||||
ToolTip.text: currentRoom ? currentRoom.usersTyping : ""
|
||||
|
||||
Keys.onReturnPressed: {
|
||||
if (event.modifiers & Qt.ShiftModifier) {
|
||||
insert(cursorPosition, "\n")
|
||||
} else {
|
||||
} else if (text) {
|
||||
postMessage(text)
|
||||
text = ""
|
||||
closeAll()
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onEscapePressed: closeAll()
|
||||
|
||||
Keys.onBacktabPressed: if (isAutoCompleting) autoCompleteListView.decrementCurrentIndex()
|
||||
|
||||
Keys.onTabPressed: {
|
||||
|
@ -259,8 +313,7 @@ Rectangle {
|
|||
var PREFIX_MARKDOWN = '/md '
|
||||
|
||||
if (isReply) {
|
||||
currentRoom.sendReply(replyUserID, replyEventID, replyContent, text)
|
||||
clearReply()
|
||||
currentRoom.sendReply(replyUser.id, replyEventID, replyContent, text)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -294,44 +347,26 @@ Rectangle {
|
|||
}
|
||||
if (text.indexOf(PREFIX_MARKDOWN) === 0) {
|
||||
text = text.substr(PREFIX_MARKDOWN.length)
|
||||
var parsedText = Markdown.markdown_parser(text)
|
||||
currentRoom.postHtmlMessage(text, parsedText, RoomMessageEvent.Text)
|
||||
currentRoom.postMarkdownText(text)
|
||||
return
|
||||
}
|
||||
|
||||
currentRoom.postPlainText(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
Layout.preferredWidth: 48
|
||||
Layout.preferredHeight: 48
|
||||
ToolButton {
|
||||
Layout.preferredWidth: 48
|
||||
Layout.preferredHeight: 48
|
||||
Layout.alignment: Qt.AlignBottom
|
||||
|
||||
id: emojiButton
|
||||
id: emojiButton
|
||||
|
||||
contentItem: MaterialIcon {
|
||||
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
|
||||
contentItem: MaterialIcon {
|
||||
icon: "\ue24e"
|
||||
}
|
||||
|
||||
Material.elevation: 2
|
||||
|
||||
textArea: inputField
|
||||
onClicked: emojiPicker.visible = !emojiPicker.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -346,8 +381,18 @@ Rectangle {
|
|||
|
||||
function clearReply() {
|
||||
isReply = false
|
||||
replyUserID = ""
|
||||
replyUser = null
|
||||
replyEventID = ""
|
||||
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
|
||||
import QtQuick 2.9
|
||||
import QtQuick 2.12
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
Settings {
|
||||
property bool showNotification: true
|
||||
|
||||
property bool pressAndHold
|
||||
property bool showTray: true
|
||||
property bool confirmOnExit: true
|
||||
|
||||
property bool darkTheme
|
||||
|
||||
property bool enableTimelineBackground: true
|
||||
property string timelineBackground
|
||||
}
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Spectral.Setting
|
||||
singleton MSettings 0.1 Setting.qml
|
||||
|
||||
singleton MPalette 0.1 Palette.qml
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
Copyright (c) 2014, John MacFarlane
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
-----
|
||||
|
||||
houdini.h, houdini_href_e.c, houdini_html_e.c, houdini_html_u.c
|
||||
|
||||
derive from https://github.com/vmg/houdini (with some modifications)
|
||||
|
||||
Copyright (C) 2012 Vicent Martí
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-----
|
||||
|
||||
buffer.h, buffer.c, chunk.h
|
||||
|
||||
are derived from code (C) 2012 Github, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in
|
||||
the Software without restriction, including without limitation the rights to
|
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
of the Software, and to permit persons to whom the Software is furnished to do
|
||||
so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
-----
|
||||
|
||||
utf8.c and utf8.c
|
||||
|
||||
are derived from utf8proc
|
||||
(<http://www.public-software-group.org/utf8proc>),
|
||||
(C) 2009 Public Software Group e. V., Berlin, Germany.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-----
|
||||
|
||||
The normalization code in normalize.py was derived from the
|
||||
markdowntest project, Copyright 2013 Karl Dubost:
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2013 Karl Dubost
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-----
|
||||
|
||||
The CommonMark spec (test/spec.txt) is
|
||||
|
||||
Copyright (C) 2014-15 John MacFarlane
|
||||
|
||||
Released under the Creative Commons CC-BY-SA 4.0 license:
|
||||
<http://creativecommons.org/licenses/by-sa/4.0/>.
|
||||
|
||||
-----
|
||||
|
||||
The test software in test/ is
|
||||
|
||||
Copyright (c) 2014, John MacFarlane
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,279 @@
|
|||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <limits.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "cmark_ctype.h"
|
||||
#include "buffer.h"
|
||||
#include "memory.h"
|
||||
|
||||
/* Used as default value for cmark_strbuf->ptr so that people can always
|
||||
* assume ptr is non-NULL and zero terminated even for new cmark_strbufs.
|
||||
*/
|
||||
unsigned char cmark_strbuf__initbuf[1];
|
||||
|
||||
#ifndef MIN
|
||||
#define MIN(x, y) ((x < y) ? x : y)
|
||||
#endif
|
||||
|
||||
void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf,
|
||||
bufsize_t initial_size) {
|
||||
buf->mem = mem;
|
||||
buf->asize = 0;
|
||||
buf->size = 0;
|
||||
buf->ptr = cmark_strbuf__initbuf;
|
||||
|
||||
if (initial_size > 0)
|
||||
cmark_strbuf_grow(buf, initial_size);
|
||||
}
|
||||
|
||||
static CMARK_INLINE void S_strbuf_grow_by(cmark_strbuf *buf, bufsize_t add) {
|
||||
cmark_strbuf_grow(buf, buf->size + add);
|
||||
}
|
||||
|
||||
void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size) {
|
||||
assert(target_size > 0);
|
||||
|
||||
if (target_size < buf->asize)
|
||||
return;
|
||||
|
||||
if (target_size > (bufsize_t)(INT32_MAX / 2)) {
|
||||
fprintf(stderr,
|
||||
"[cmark] cmark_strbuf_grow requests buffer with size > %d, aborting\n",
|
||||
(INT32_MAX / 2));
|
||||
abort();
|
||||
}
|
||||
|
||||
/* Oversize the buffer by 50% to guarantee amortized linear time
|
||||
* complexity on append operations. */
|
||||
bufsize_t new_size = target_size + target_size / 2;
|
||||
new_size += 1;
|
||||
new_size = (new_size + 7) & ~7;
|
||||
|
||||
buf->ptr = (unsigned char *)buf->mem->realloc(buf->asize ? buf->ptr : NULL,
|
||||
new_size);
|
||||
buf->asize = new_size;
|
||||
}
|
||||
|
||||
bufsize_t cmark_strbuf_len(const cmark_strbuf *buf) { return buf->size; }
|
||||
|
||||
void cmark_strbuf_free(cmark_strbuf *buf) {
|
||||
if (!buf)
|
||||
return;
|
||||
|
||||
if (buf->ptr != cmark_strbuf__initbuf)
|
||||
buf->mem->free(buf->ptr);
|
||||
|
||||
cmark_strbuf_init(buf->mem, buf, 0);
|
||||
}
|
||||
|
||||
void cmark_strbuf_clear(cmark_strbuf *buf) {
|
||||
buf->size = 0;
|
||||
|
||||
if (buf->asize > 0)
|
||||
buf->ptr[0] = '\0';
|
||||
}
|
||||
|
||||
void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data,
|
||||
bufsize_t len) {
|
||||
if (len <= 0 || data == NULL) {
|
||||
cmark_strbuf_clear(buf);
|
||||
} else {
|
||||
if (data != buf->ptr) {
|
||||
if (len >= buf->asize)
|
||||
cmark_strbuf_grow(buf, len);
|
||||
memmove(buf->ptr, data, len);
|
||||
}
|
||||
buf->size = len;
|
||||
buf->ptr[buf->size] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void cmark_strbuf_sets(cmark_strbuf *buf, const char *string) {
|
||||
cmark_strbuf_set(buf, (const unsigned char *)string,
|
||||
string ? strlen(string) : 0);
|
||||
}
|
||||
|
||||
void cmark_strbuf_putc(cmark_strbuf *buf, int c) {
|
||||
S_strbuf_grow_by(buf, 1);
|
||||
buf->ptr[buf->size++] = (unsigned char)(c & 0xFF);
|
||||
buf->ptr[buf->size] = '\0';
|
||||
}
|
||||
|
||||
void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data,
|
||||
bufsize_t len) {
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
S_strbuf_grow_by(buf, len);
|
||||
memmove(buf->ptr + buf->size, data, len);
|
||||
buf->size += len;
|
||||
buf->ptr[buf->size] = '\0';
|
||||
}
|
||||
|
||||
void cmark_strbuf_puts(cmark_strbuf *buf, const char *string) {
|
||||
cmark_strbuf_put(buf, (const unsigned char *)string, strlen(string));
|
||||
}
|
||||
|
||||
void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize,
|
||||
const cmark_strbuf *buf) {
|
||||
bufsize_t copylen;
|
||||
|
||||
assert(buf);
|
||||
if (!data || datasize <= 0)
|
||||
return;
|
||||
|
||||
data[0] = '\0';
|
||||
|
||||
if (buf->size == 0 || buf->asize <= 0)
|
||||
return;
|
||||
|
||||
copylen = buf->size;
|
||||
if (copylen > datasize - 1)
|
||||
copylen = datasize - 1;
|
||||
memmove(data, buf->ptr, copylen);
|
||||
data[copylen] = '\0';
|
||||
}
|
||||
|
||||
void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b) {
|
||||
cmark_strbuf t = *buf_a;
|
||||
*buf_a = *buf_b;
|
||||
*buf_b = t;
|
||||
}
|
||||
|
||||
unsigned char *cmark_strbuf_detach(cmark_strbuf *buf) {
|
||||
unsigned char *data = buf->ptr;
|
||||
|
||||
if (buf->asize == 0) {
|
||||
/* return an empty string */
|
||||
return (unsigned char *)buf->mem->calloc(1, 1);
|
||||
}
|
||||
|
||||
cmark_strbuf_init(buf->mem, buf, 0);
|
||||
return data;
|
||||
}
|
||||
|
||||
int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b) {
|
||||
int result = memcmp(a->ptr, b->ptr, MIN(a->size, b->size));
|
||||
return (result != 0) ? result
|
||||
: (a->size < b->size) ? -1 : (a->size > b->size) ? 1 : 0;
|
||||
}
|
||||
|
||||
bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos) {
|
||||
if (pos >= buf->size)
|
||||
return -1;
|
||||
if (pos < 0)
|
||||
pos = 0;
|
||||
|
||||
const unsigned char *p =
|
||||
(unsigned char *)memchr(buf->ptr + pos, c, buf->size - pos);
|
||||
if (!p)
|
||||
return -1;
|
||||
|
||||
return (bufsize_t)(p - (const unsigned char *)buf->ptr);
|
||||
}
|
||||
|
||||
bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos) {
|
||||
if (pos < 0 || buf->size == 0)
|
||||
return -1;
|
||||
if (pos >= buf->size)
|
||||
pos = buf->size - 1;
|
||||
|
||||
bufsize_t i;
|
||||
for (i = pos; i >= 0; i--) {
|
||||
if (buf->ptr[i] == (unsigned char)c)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len) {
|
||||
if (len < 0)
|
||||
len = 0;
|
||||
|
||||
if (len < buf->size) {
|
||||
buf->size = len;
|
||||
buf->ptr[buf->size] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n) {
|
||||
if (n > 0) {
|
||||
if (n > buf->size)
|
||||
n = buf->size;
|
||||
buf->size = buf->size - n;
|
||||
if (buf->size)
|
||||
memmove(buf->ptr, buf->ptr + n, buf->size);
|
||||
|
||||
buf->ptr[buf->size] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void cmark_strbuf_rtrim(cmark_strbuf *buf) {
|
||||
if (!buf->size)
|
||||
return;
|
||||
|
||||
while (buf->size > 0) {
|
||||
if (!cmark_isspace(buf->ptr[buf->size - 1]))
|
||||
break;
|
||||
|
||||
buf->size--;
|
||||
}
|
||||
|
||||
buf->ptr[buf->size] = '\0';
|
||||
}
|
||||
|
||||
void cmark_strbuf_trim(cmark_strbuf *buf) {
|
||||
bufsize_t i = 0;
|
||||
|
||||
if (!buf->size)
|
||||
return;
|
||||
|
||||
while (i < buf->size && cmark_isspace(buf->ptr[i]))
|
||||
i++;
|
||||
|
||||
cmark_strbuf_drop(buf, i);
|
||||
|
||||
cmark_strbuf_rtrim(buf);
|
||||
}
|
||||
|
||||
// Destructively modify string, collapsing consecutive
|
||||
// space and newline characters into a single space.
|
||||
void cmark_strbuf_normalize_whitespace(cmark_strbuf *s) {
|
||||
bool last_char_was_space = false;
|
||||
bufsize_t r, w;
|
||||
|
||||
for (r = 0, w = 0; r < s->size; ++r) {
|
||||
if (cmark_isspace(s->ptr[r])) {
|
||||
if (!last_char_was_space) {
|
||||
s->ptr[w++] = ' ';
|
||||
last_char_was_space = true;
|
||||
}
|
||||
} else {
|
||||
s->ptr[w++] = s->ptr[r];
|
||||
last_char_was_space = false;
|
||||
}
|
||||
}
|
||||
|
||||
cmark_strbuf_truncate(s, w);
|
||||
}
|
||||
|
||||
// Destructively unescape a string: remove backslashes before punctuation chars.
|
||||
extern void cmark_strbuf_unescape(cmark_strbuf *buf) {
|
||||
bufsize_t r, w;
|
||||
|
||||
for (r = 0, w = 0; r < buf->size; ++r) {
|
||||
if (buf->ptr[r] == '\\' && cmark_ispunct(buf->ptr[r + 1]))
|
||||
r++;
|
||||
|
||||
buf->ptr[w++] = buf->ptr[r];
|
||||
}
|
||||
|
||||
cmark_strbuf_truncate(buf, w);
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
#ifndef CMARK_BUFFER_H
|
||||
#define CMARK_BUFFER_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <stdint.h>
|
||||
#include "config.h"
|
||||
#include "cmark.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef int32_t bufsize_t;
|
||||
|
||||
typedef struct {
|
||||
cmark_mem *mem;
|
||||
unsigned char *ptr;
|
||||
bufsize_t asize, size;
|
||||
} cmark_strbuf;
|
||||
|
||||
extern unsigned char cmark_strbuf__initbuf[];
|
||||
|
||||
#define CMARK_BUF_INIT(mem) \
|
||||
{ mem, cmark_strbuf__initbuf, 0, 0 }
|
||||
|
||||
/**
|
||||
* Initialize a cmark_strbuf structure.
|
||||
*
|
||||
* For the cases where CMARK_BUF_INIT cannot be used to do static
|
||||
* initialization.
|
||||
*/
|
||||
void cmark_strbuf_init(cmark_mem *mem, cmark_strbuf *buf,
|
||||
bufsize_t initial_size);
|
||||
|
||||
/**
|
||||
* Grow the buffer to hold at least `target_size` bytes.
|
||||
*/
|
||||
void cmark_strbuf_grow(cmark_strbuf *buf, bufsize_t target_size);
|
||||
|
||||
void cmark_strbuf_free(cmark_strbuf *buf);
|
||||
void cmark_strbuf_swap(cmark_strbuf *buf_a, cmark_strbuf *buf_b);
|
||||
|
||||
bufsize_t cmark_strbuf_len(const cmark_strbuf *buf);
|
||||
|
||||
int cmark_strbuf_cmp(const cmark_strbuf *a, const cmark_strbuf *b);
|
||||
|
||||
unsigned char *cmark_strbuf_detach(cmark_strbuf *buf);
|
||||
void cmark_strbuf_copy_cstr(char *data, bufsize_t datasize,
|
||||
const cmark_strbuf *buf);
|
||||
|
||||
static CMARK_INLINE const char *cmark_strbuf_cstr(const cmark_strbuf *buf) {
|
||||
return (char *)buf->ptr;
|
||||
}
|
||||
|
||||
#define cmark_strbuf_at(buf, n) ((buf)->ptr[n])
|
||||
|
||||
void cmark_strbuf_set(cmark_strbuf *buf, const unsigned char *data,
|
||||
bufsize_t len);
|
||||
void cmark_strbuf_sets(cmark_strbuf *buf, const char *string);
|
||||
void cmark_strbuf_putc(cmark_strbuf *buf, int c);
|
||||
void cmark_strbuf_put(cmark_strbuf *buf, const unsigned char *data,
|
||||
bufsize_t len);
|
||||
void cmark_strbuf_puts(cmark_strbuf *buf, const char *string);
|
||||
void cmark_strbuf_clear(cmark_strbuf *buf);
|
||||
|
||||
bufsize_t cmark_strbuf_strchr(const cmark_strbuf *buf, int c, bufsize_t pos);
|
||||
bufsize_t cmark_strbuf_strrchr(const cmark_strbuf *buf, int c, bufsize_t pos);
|
||||
void cmark_strbuf_drop(cmark_strbuf *buf, bufsize_t n);
|
||||
void cmark_strbuf_truncate(cmark_strbuf *buf, bufsize_t len);
|
||||
void cmark_strbuf_rtrim(cmark_strbuf *buf);
|
||||
void cmark_strbuf_trim(cmark_strbuf *buf);
|
||||
void cmark_strbuf_normalize_whitespace(cmark_strbuf *s);
|
||||
void cmark_strbuf_unescape(cmark_strbuf *s);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,120 @@
|
|||
#ifndef CMARK_CHUNK_H
|
||||
#define CMARK_CHUNK_H
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include "cmark.h"
|
||||
#include "buffer.h"
|
||||
#include "memory.h"
|
||||
#include "cmark_ctype.h"
|
||||
|
||||
#define CMARK_CHUNK_EMPTY \
|
||||
{ NULL, 0, 0 }
|
||||
|
||||
typedef struct {
|
||||
unsigned char *data;
|
||||
bufsize_t len;
|
||||
bufsize_t alloc; // also implies a NULL-terminated string
|
||||
} cmark_chunk;
|
||||
|
||||
static CMARK_INLINE void cmark_chunk_free(cmark_mem *mem, cmark_chunk *c) {
|
||||
if (c->alloc)
|
||||
mem->free(c->data);
|
||||
|
||||
c->data = NULL;
|
||||
c->alloc = 0;
|
||||
c->len = 0;
|
||||
}
|
||||
|
||||
static CMARK_INLINE void cmark_chunk_ltrim(cmark_chunk *c) {
|
||||
assert(!c->alloc);
|
||||
|
||||
while (c->len && cmark_isspace(c->data[0])) {
|
||||
c->data++;
|
||||
c->len--;
|
||||
}
|
||||
}
|
||||
|
||||
static CMARK_INLINE void cmark_chunk_rtrim(cmark_chunk *c) {
|
||||
assert(!c->alloc);
|
||||
|
||||
while (c->len > 0) {
|
||||
if (!cmark_isspace(c->data[c->len - 1]))
|
||||
break;
|
||||
|
||||
c->len--;
|
||||
}
|
||||
}
|
||||
|
||||
static CMARK_INLINE void cmark_chunk_trim(cmark_chunk *c) {
|
||||
cmark_chunk_ltrim(c);
|
||||
cmark_chunk_rtrim(c);
|
||||
}
|
||||
|
||||
static CMARK_INLINE bufsize_t cmark_chunk_strchr(cmark_chunk *ch, int c,
|
||||
bufsize_t offset) {
|
||||
const unsigned char *p =
|
||||
(unsigned char *)memchr(ch->data + offset, c, ch->len - offset);
|
||||
return p ? (bufsize_t)(p - ch->data) : ch->len;
|
||||
}
|
||||
|
||||
static CMARK_INLINE const char *cmark_chunk_to_cstr(cmark_mem *mem,
|
||||
cmark_chunk *c) {
|
||||
unsigned char *str;
|
||||
|
||||
if (c->alloc) {
|
||||
return (char *)c->data;
|
||||
}
|
||||
str = (unsigned char *)mem->calloc(c->len + 1, 1);
|
||||
if (c->len > 0) {
|
||||
memcpy(str, c->data, c->len);
|
||||
}
|
||||
str[c->len] = 0;
|
||||
c->data = str;
|
||||
c->alloc = 1;
|
||||
|
||||
return (char *)str;
|
||||
}
|
||||
|
||||
static CMARK_INLINE void cmark_chunk_set_cstr(cmark_mem *mem, cmark_chunk *c,
|
||||
const char *str) {
|
||||
unsigned char *old = c->alloc ? c->data : NULL;
|
||||
if (str == NULL) {
|
||||
c->len = 0;
|
||||
c->data = NULL;
|
||||
c->alloc = 0;
|
||||
} else {
|
||||
c->len = (bufsize_t)strlen(str);
|
||||
c->data = (unsigned char *)mem->calloc(c->len + 1, 1);
|
||||
c->alloc = 1;
|
||||
memcpy(c->data, str, c->len + 1);
|
||||
}
|
||||
if (old != NULL) {
|
||||
mem->free(old);
|
||||
}
|
||||
}
|
||||
|
||||
static CMARK_INLINE cmark_chunk cmark_chunk_literal(const char *data) {
|
||||
bufsize_t len = data ? (bufsize_t)strlen(data) : 0;
|
||||
cmark_chunk c = {(unsigned char *)data, len, 0};
|
||||
return c;
|
||||
}
|
||||
|
||||
static CMARK_INLINE cmark_chunk cmark_chunk_dup(const cmark_chunk *ch,
|
||||
bufsize_t pos, bufsize_t len) {
|
||||
cmark_chunk c = {ch->data + pos, len, 0};
|
||||
return c;
|
||||
}
|
||||
|
||||
static CMARK_INLINE cmark_chunk cmark_chunk_buf_detach(cmark_strbuf *buf) {
|
||||
cmark_chunk c;
|
||||
|
||||
c.len = buf->size;
|
||||
c.data = cmark_strbuf_detach(buf);
|
||||
c.alloc = 1;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,43 @@
|
|||
#include <stdlib.h>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include "node.h"
|
||||
#include "houdini.h"
|
||||
#include "cmark.h"
|
||||
#include "buffer.h"
|
||||
|
||||
int cmark_version() { return CMARK_VERSION; }
|
||||
|
||||
const char *cmark_version_string() { return CMARK_VERSION_STRING; }
|
||||
|
||||
static void *xcalloc(size_t nmem, size_t size) {
|
||||
void *ptr = calloc(nmem, size);
|
||||
if (!ptr) {
|
||||
fprintf(stderr, "[cmark] calloc returned null pointer, aborting\n");
|
||||
abort();
|
||||
}
|
||||
return ptr;
|
||||
}
|
||||
|
||||
static void *xrealloc(void *ptr, size_t size) {
|
||||
void *new_ptr = realloc(ptr, size);
|
||||
if (!new_ptr) {
|
||||
fprintf(stderr, "[cmark] realloc returned null pointer, aborting\n");
|
||||
abort();
|
||||
}
|
||||
return new_ptr;
|
||||
}
|
||||
|
||||
cmark_mem DEFAULT_MEM_ALLOCATOR = {xcalloc, xrealloc, free};
|
||||
|
||||
char *cmark_markdown_to_html(const char *text, size_t len, int options) {
|
||||
cmark_node *doc;
|
||||
char *result;
|
||||
|
||||
doc = cmark_parse_document(text, len, options);
|
||||
|
||||
result = cmark_render_html(doc, options);
|
||||
cmark_node_free(doc);
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,644 @@
|
|||
#ifndef CMARK_H
|
||||
#define CMARK_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <cmark_export.h>
|
||||
#include <cmark_version.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** # NAME
|
||||
*
|
||||
* **cmark** - CommonMark parsing, manipulating, and rendering
|
||||
*/
|
||||
|
||||
/** # DESCRIPTION
|
||||
*
|
||||
* ## Simple Interface
|
||||
*/
|
||||
|
||||
/** Convert 'text' (assumed to be a UTF-8 encoded string with length
|
||||
* 'len') from CommonMark Markdown to HTML, returning a null-terminated,
|
||||
* UTF-8-encoded string. It is the caller's responsibility
|
||||
* to free the returned buffer.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
char *cmark_markdown_to_html(const char *text, size_t len, int options);
|
||||
|
||||
/** ## Node Structure
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
/* Error status */
|
||||
CMARK_NODE_NONE,
|
||||
|
||||
/* Block */
|
||||
CMARK_NODE_DOCUMENT,
|
||||
CMARK_NODE_BLOCK_QUOTE,
|
||||
CMARK_NODE_LIST,
|
||||
CMARK_NODE_ITEM,
|
||||
CMARK_NODE_CODE_BLOCK,
|
||||
CMARK_NODE_HTML_BLOCK,
|
||||
CMARK_NODE_CUSTOM_BLOCK,
|
||||
CMARK_NODE_PARAGRAPH,
|
||||
CMARK_NODE_HEADING,
|
||||
CMARK_NODE_THEMATIC_BREAK,
|
||||
|
||||
CMARK_NODE_FIRST_BLOCK = CMARK_NODE_DOCUMENT,
|
||||
CMARK_NODE_LAST_BLOCK = CMARK_NODE_THEMATIC_BREAK,
|
||||
|
||||
/* Inline */
|
||||
CMARK_NODE_TEXT,
|
||||
CMARK_NODE_SOFTBREAK,
|
||||
CMARK_NODE_LINEBREAK,
|
||||
CMARK_NODE_CODE,
|
||||
CMARK_NODE_HTML_INLINE,
|
||||
CMARK_NODE_CUSTOM_INLINE,
|
||||
CMARK_NODE_EMPH,
|
||||
CMARK_NODE_STRONG,
|
||||
CMARK_NODE_LINK,
|
||||
CMARK_NODE_IMAGE,
|
||||
|
||||
CMARK_NODE_FIRST_INLINE = CMARK_NODE_TEXT,
|
||||
CMARK_NODE_LAST_INLINE = CMARK_NODE_IMAGE,
|
||||
} cmark_node_type;
|
||||
|
||||
/* For backwards compatibility: */
|
||||
#define CMARK_NODE_HEADER CMARK_NODE_HEADING
|
||||
#define CMARK_NODE_HRULE CMARK_NODE_THEMATIC_BREAK
|
||||
#define CMARK_NODE_HTML CMARK_NODE_HTML_BLOCK
|
||||
#define CMARK_NODE_INLINE_HTML CMARK_NODE_HTML_INLINE
|
||||
|
||||
typedef enum {
|
||||
CMARK_NO_LIST,
|
||||
CMARK_BULLET_LIST,
|
||||
CMARK_ORDERED_LIST
|
||||
} cmark_list_type;
|
||||
|
||||
typedef enum {
|
||||
CMARK_NO_DELIM,
|
||||
CMARK_PERIOD_DELIM,
|
||||
CMARK_PAREN_DELIM
|
||||
} cmark_delim_type;
|
||||
|
||||
typedef struct cmark_node cmark_node;
|
||||
typedef struct cmark_parser cmark_parser;
|
||||
typedef struct cmark_iter cmark_iter;
|
||||
|
||||
/**
|
||||
* ## Custom memory allocator support
|
||||
*/
|
||||
|
||||
/** Defines the memory allocation functions to be used by CMark
|
||||
* when parsing and allocating a document tree
|
||||
*/
|
||||
typedef struct cmark_mem {
|
||||
void *(*calloc)(size_t, size_t);
|
||||
void *(*realloc)(void *, size_t);
|
||||
void (*free)(void *);
|
||||
} cmark_mem;
|
||||
|
||||
/**
|
||||
* ## Creating and Destroying Nodes
|
||||
*/
|
||||
|
||||
/** Creates a new node of type 'type'. Note that the node may have
|
||||
* other required properties, which it is the caller's responsibility
|
||||
* to assign.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node *cmark_node_new(cmark_node_type type);
|
||||
|
||||
/** Same as `cmark_node_new`, but explicitly listing the memory
|
||||
* allocator used to allocate the node. Note: be sure to use the same
|
||||
* allocator for every node in a tree, or bad things can happen.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node *cmark_node_new_with_mem(cmark_node_type type,
|
||||
cmark_mem *mem);
|
||||
|
||||
/** Frees the memory allocated for a node and any children.
|
||||
*/
|
||||
CMARK_EXPORT void cmark_node_free(cmark_node *node);
|
||||
|
||||
/**
|
||||
* ## Tree Traversal
|
||||
*/
|
||||
|
||||
/** Returns the next node in the sequence after 'node', or NULL if
|
||||
* there is none.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node *cmark_node_next(cmark_node *node);
|
||||
|
||||
/** Returns the previous node in the sequence after 'node', or NULL if
|
||||
* there is none.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node *cmark_node_previous(cmark_node *node);
|
||||
|
||||
/** Returns the parent of 'node', or NULL if there is none.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node *cmark_node_parent(cmark_node *node);
|
||||
|
||||
/** Returns the first child of 'node', or NULL if 'node' has no children.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node *cmark_node_first_child(cmark_node *node);
|
||||
|
||||
/** Returns the last child of 'node', or NULL if 'node' has no children.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node *cmark_node_last_child(cmark_node *node);
|
||||
|
||||
/**
|
||||
* ## Iterator
|
||||
*
|
||||
* An iterator will walk through a tree of nodes, starting from a root
|
||||
* node, returning one node at a time, together with information about
|
||||
* whether the node is being entered or exited. The iterator will
|
||||
* first descend to a child node, if there is one. When there is no
|
||||
* child, the iterator will go to the next sibling. When there is no
|
||||
* next sibling, the iterator will return to the parent (but with
|
||||
* a 'cmark_event_type' of `CMARK_EVENT_EXIT`). The iterator will
|
||||
* return `CMARK_EVENT_DONE` when it reaches the root node again.
|
||||
* One natural application is an HTML renderer, where an `ENTER` event
|
||||
* outputs an open tag and an `EXIT` event outputs a close tag.
|
||||
* An iterator might also be used to transform an AST in some systematic
|
||||
* way, for example, turning all level-3 headings into regular paragraphs.
|
||||
*
|
||||
* void
|
||||
* usage_example(cmark_node *root) {
|
||||
* cmark_event_type ev_type;
|
||||
* cmark_iter *iter = cmark_iter_new(root);
|
||||
*
|
||||
* while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
||||
* cmark_node *cur = cmark_iter_get_node(iter);
|
||||
* // Do something with `cur` and `ev_type`
|
||||
* }
|
||||
*
|
||||
* cmark_iter_free(iter);
|
||||
* }
|
||||
*
|
||||
* Iterators will never return `EXIT` events for leaf nodes, which are nodes
|
||||
* of type:
|
||||
*
|
||||
* * CMARK_NODE_HTML_BLOCK
|
||||
* * CMARK_NODE_THEMATIC_BREAK
|
||||
* * CMARK_NODE_CODE_BLOCK
|
||||
* * CMARK_NODE_TEXT
|
||||
* * CMARK_NODE_SOFTBREAK
|
||||
* * CMARK_NODE_LINEBREAK
|
||||
* * CMARK_NODE_CODE
|
||||
* * CMARK_NODE_HTML_INLINE
|
||||
*
|
||||
* Nodes must only be modified after an `EXIT` event, or an `ENTER` event for
|
||||
* leaf nodes.
|
||||
*/
|
||||
|
||||
typedef enum {
|
||||
CMARK_EVENT_NONE,
|
||||
CMARK_EVENT_DONE,
|
||||
CMARK_EVENT_ENTER,
|
||||
CMARK_EVENT_EXIT
|
||||
} cmark_event_type;
|
||||
|
||||
/** Creates a new iterator starting at 'root'. The current node and event
|
||||
* type are undefined until 'cmark_iter_next' is called for the first time.
|
||||
* The memory allocated for the iterator should be released using
|
||||
* 'cmark_iter_free' when it is no longer needed.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_iter *cmark_iter_new(cmark_node *root);
|
||||
|
||||
/** Frees the memory allocated for an iterator.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
void cmark_iter_free(cmark_iter *iter);
|
||||
|
||||
/** Advances to the next node and returns the event type (`CMARK_EVENT_ENTER`,
|
||||
* `CMARK_EVENT_EXIT` or `CMARK_EVENT_DONE`).
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_event_type cmark_iter_next(cmark_iter *iter);
|
||||
|
||||
/** Returns the current node.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_node *cmark_iter_get_node(cmark_iter *iter);
|
||||
|
||||
/** Returns the current event type.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_event_type cmark_iter_get_event_type(cmark_iter *iter);
|
||||
|
||||
/** Returns the root node.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_node *cmark_iter_get_root(cmark_iter *iter);
|
||||
|
||||
/** Resets the iterator so that the current node is 'current' and
|
||||
* the event type is 'event_type'. The new current node must be a
|
||||
* descendant of the root node or the root node itself.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
void cmark_iter_reset(cmark_iter *iter, cmark_node *current,
|
||||
cmark_event_type event_type);
|
||||
|
||||
/**
|
||||
* ## Accessors
|
||||
*/
|
||||
|
||||
/** Returns the user data of 'node'.
|
||||
*/
|
||||
CMARK_EXPORT void *cmark_node_get_user_data(cmark_node *node);
|
||||
|
||||
/** Sets arbitrary user data for 'node'. Returns 1 on success,
|
||||
* 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_user_data(cmark_node *node, void *user_data);
|
||||
|
||||
/** Returns the type of 'node', or `CMARK_NODE_NONE` on error.
|
||||
*/
|
||||
CMARK_EXPORT cmark_node_type cmark_node_get_type(cmark_node *node);
|
||||
|
||||
/** Like 'cmark_node_get_type', but returns a string representation
|
||||
of the type, or `"<unknown>"`.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
const char *cmark_node_get_type_string(cmark_node *node);
|
||||
|
||||
/** Returns the string contents of 'node', or an empty
|
||||
string if none is set. Returns NULL if called on a
|
||||
node that does not have string content.
|
||||
*/
|
||||
CMARK_EXPORT const char *cmark_node_get_literal(cmark_node *node);
|
||||
|
||||
/** Sets the string contents of 'node'. Returns 1 on success,
|
||||
* 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_literal(cmark_node *node, const char *content);
|
||||
|
||||
/** Returns the heading level of 'node', or 0 if 'node' is not a heading.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_get_heading_level(cmark_node *node);
|
||||
|
||||
/* For backwards compatibility */
|
||||
#define cmark_node_get_header_level cmark_node_get_heading_level
|
||||
#define cmark_node_set_header_level cmark_node_set_heading_level
|
||||
|
||||
/** Sets the heading level of 'node', returning 1 on success and 0 on error.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_heading_level(cmark_node *node, int level);
|
||||
|
||||
/** Returns the list type of 'node', or `CMARK_NO_LIST` if 'node'
|
||||
* is not a list.
|
||||
*/
|
||||
CMARK_EXPORT cmark_list_type cmark_node_get_list_type(cmark_node *node);
|
||||
|
||||
/** Sets the list type of 'node', returning 1 on success and 0 on error.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_list_type(cmark_node *node,
|
||||
cmark_list_type type);
|
||||
|
||||
/** Returns the list delimiter type of 'node', or `CMARK_NO_DELIM` if 'node'
|
||||
* is not a list.
|
||||
*/
|
||||
CMARK_EXPORT cmark_delim_type cmark_node_get_list_delim(cmark_node *node);
|
||||
|
||||
/** Sets the list delimiter type of 'node', returning 1 on success and 0
|
||||
* on error.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_list_delim(cmark_node *node,
|
||||
cmark_delim_type delim);
|
||||
|
||||
/** Returns starting number of 'node', if it is an ordered list, otherwise 0.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_get_list_start(cmark_node *node);
|
||||
|
||||
/** Sets starting number of 'node', if it is an ordered list. Returns 1
|
||||
* on success, 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_list_start(cmark_node *node, int start);
|
||||
|
||||
/** Returns 1 if 'node' is a tight list, 0 otherwise.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_get_list_tight(cmark_node *node);
|
||||
|
||||
/** Sets the "tightness" of a list. Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_list_tight(cmark_node *node, int tight);
|
||||
|
||||
/** Returns the info string from a fenced code block.
|
||||
*/
|
||||
CMARK_EXPORT const char *cmark_node_get_fence_info(cmark_node *node);
|
||||
|
||||
/** Sets the info string in a fenced code block, returning 1 on
|
||||
* success and 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_fence_info(cmark_node *node, const char *info);
|
||||
|
||||
/** Returns the URL of a link or image 'node', or an empty string
|
||||
if no URL is set. Returns NULL if called on a node that is
|
||||
not a link or image.
|
||||
*/
|
||||
CMARK_EXPORT const char *cmark_node_get_url(cmark_node *node);
|
||||
|
||||
/** Sets the URL of a link or image 'node'. Returns 1 on success,
|
||||
* 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_url(cmark_node *node, const char *url);
|
||||
|
||||
/** Returns the title of a link or image 'node', or an empty
|
||||
string if no title is set. Returns NULL if called on a node
|
||||
that is not a link or image.
|
||||
*/
|
||||
CMARK_EXPORT const char *cmark_node_get_title(cmark_node *node);
|
||||
|
||||
/** Sets the title of a link or image 'node'. Returns 1 on success,
|
||||
* 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_title(cmark_node *node, const char *title);
|
||||
|
||||
/** Returns the literal "on enter" text for a custom 'node', or
|
||||
an empty string if no on_enter is set. Returns NULL if called
|
||||
on a non-custom node.
|
||||
*/
|
||||
CMARK_EXPORT const char *cmark_node_get_on_enter(cmark_node *node);
|
||||
|
||||
/** Sets the literal text to render "on enter" for a custom 'node'.
|
||||
Any children of the node will be rendered after this text.
|
||||
Returns 1 on success 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_on_enter(cmark_node *node,
|
||||
const char *on_enter);
|
||||
|
||||
/** Returns the literal "on exit" text for a custom 'node', or
|
||||
an empty string if no on_exit is set. Returns NULL if
|
||||
called on a non-custom node.
|
||||
*/
|
||||
CMARK_EXPORT const char *cmark_node_get_on_exit(cmark_node *node);
|
||||
|
||||
/** Sets the literal text to render "on exit" for a custom 'node'.
|
||||
Any children of the node will be rendered before this text.
|
||||
Returns 1 on success 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_set_on_exit(cmark_node *node, const char *on_exit);
|
||||
|
||||
/** Returns the line on which 'node' begins.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_get_start_line(cmark_node *node);
|
||||
|
||||
/** Returns the column at which 'node' begins.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_get_start_column(cmark_node *node);
|
||||
|
||||
/** Returns the line on which 'node' ends.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_get_end_line(cmark_node *node);
|
||||
|
||||
/** Returns the column at which 'node' ends.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_get_end_column(cmark_node *node);
|
||||
|
||||
/**
|
||||
* ## Tree Manipulation
|
||||
*/
|
||||
|
||||
/** Unlinks a 'node', removing it from the tree, but not freeing its
|
||||
* memory. (Use 'cmark_node_free' for that.)
|
||||
*/
|
||||
CMARK_EXPORT void cmark_node_unlink(cmark_node *node);
|
||||
|
||||
/** Inserts 'sibling' before 'node'. Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_insert_before(cmark_node *node,
|
||||
cmark_node *sibling);
|
||||
|
||||
/** Inserts 'sibling' after 'node'. Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_insert_after(cmark_node *node, cmark_node *sibling);
|
||||
|
||||
/** Replaces 'oldnode' with 'newnode' and unlinks 'oldnode' (but does
|
||||
* not free its memory).
|
||||
* Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_replace(cmark_node *oldnode, cmark_node *newnode);
|
||||
|
||||
/** Adds 'child' to the beginning of the children of 'node'.
|
||||
* Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_prepend_child(cmark_node *node, cmark_node *child);
|
||||
|
||||
/** Adds 'child' to the end of the children of 'node'.
|
||||
* Returns 1 on success, 0 on failure.
|
||||
*/
|
||||
CMARK_EXPORT int cmark_node_append_child(cmark_node *node, cmark_node *child);
|
||||
|
||||
/** Consolidates adjacent text nodes.
|
||||
*/
|
||||
CMARK_EXPORT void cmark_consolidate_text_nodes(cmark_node *root);
|
||||
|
||||
/**
|
||||
* ## Parsing
|
||||
*
|
||||
* Simple interface:
|
||||
*
|
||||
* cmark_node *document = cmark_parse_document("Hello *world*", 13,
|
||||
* CMARK_OPT_DEFAULT);
|
||||
*
|
||||
* Streaming interface:
|
||||
*
|
||||
* cmark_parser *parser = cmark_parser_new(CMARK_OPT_DEFAULT);
|
||||
* FILE *fp = fopen("myfile.md", "rb");
|
||||
* while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
|
||||
* cmark_parser_feed(parser, buffer, bytes);
|
||||
* if (bytes < sizeof(buffer)) {
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
* document = cmark_parser_finish(parser);
|
||||
* cmark_parser_free(parser);
|
||||
*/
|
||||
|
||||
/** Creates a new parser object.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_parser *cmark_parser_new(int options);
|
||||
|
||||
/** Creates a new parser object with the given memory allocator
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_parser *cmark_parser_new_with_mem(int options, cmark_mem *mem);
|
||||
|
||||
/** Frees memory allocated for a parser object.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
void cmark_parser_free(cmark_parser *parser);
|
||||
|
||||
/** Feeds a string of length 'len' to 'parser'.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
void cmark_parser_feed(cmark_parser *parser, const char *buffer, size_t len);
|
||||
|
||||
/** Finish parsing and return a pointer to a tree of nodes.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_node *cmark_parser_finish(cmark_parser *parser);
|
||||
|
||||
/** Parse a CommonMark document in 'buffer' of length 'len'.
|
||||
* Returns a pointer to a tree of nodes. The memory allocated for
|
||||
* the node tree should be released using 'cmark_node_free'
|
||||
* when it is no longer needed.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_node *cmark_parse_document(const char *buffer, size_t len, int options);
|
||||
|
||||
/** Parse a CommonMark document in file 'f', returning a pointer to
|
||||
* a tree of nodes. The memory allocated for the node tree should be
|
||||
* released using 'cmark_node_free' when it is no longer needed.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
cmark_node *cmark_parse_file(FILE *f, int options);
|
||||
|
||||
/**
|
||||
* ## Rendering
|
||||
*/
|
||||
|
||||
/** Render a 'node' tree as XML. It is the caller's responsibility
|
||||
* to free the returned buffer.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
char *cmark_render_xml(cmark_node *root, int options);
|
||||
|
||||
/** Render a 'node' tree as an HTML fragment. It is up to the user
|
||||
* to add an appropriate header and footer. It is the caller's
|
||||
* responsibility to free the returned buffer.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
char *cmark_render_html(cmark_node *root, int options);
|
||||
|
||||
/** Render a 'node' tree as a groff man page, without the header.
|
||||
* It is the caller's responsibility to free the returned buffer.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
char *cmark_render_man(cmark_node *root, int options, int width);
|
||||
|
||||
/** Render a 'node' tree as a commonmark document.
|
||||
* It is the caller's responsibility to free the returned buffer.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
char *cmark_render_commonmark(cmark_node *root, int options, int width);
|
||||
|
||||
/** Render a 'node' tree as a LaTeX document.
|
||||
* It is the caller's responsibility to free the returned buffer.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
char *cmark_render_latex(cmark_node *root, int options, int width);
|
||||
|
||||
/**
|
||||
* ## Options
|
||||
*/
|
||||
|
||||
/** Default options.
|
||||
*/
|
||||
#define CMARK_OPT_DEFAULT 0
|
||||
|
||||
/**
|
||||
* ### Options affecting rendering
|
||||
*/
|
||||
|
||||
/** Include a `data-sourcepos` attribute on all block elements.
|
||||
*/
|
||||
#define CMARK_OPT_SOURCEPOS (1 << 1)
|
||||
|
||||
/** Render `softbreak` elements as hard line breaks.
|
||||
*/
|
||||
#define CMARK_OPT_HARDBREAKS (1 << 2)
|
||||
|
||||
/** Suppress raw HTML and unsafe links (`javascript:`, `vbscript:`,
|
||||
* `file:`, and `data:`, except for `image/png`, `image/gif`,
|
||||
* `image/jpeg`, or `image/webp` mime types). Raw HTML is replaced
|
||||
* by a placeholder HTML comment. Unsafe links are replaced by
|
||||
* empty strings.
|
||||
*/
|
||||
#define CMARK_OPT_SAFE (1 << 3)
|
||||
|
||||
/** Render `softbreak` elements as spaces.
|
||||
*/
|
||||
#define CMARK_OPT_NOBREAKS (1 << 4)
|
||||
|
||||
/**
|
||||
* ### Options affecting parsing
|
||||
*/
|
||||
|
||||
/** Legacy option (no effect).
|
||||
*/
|
||||
#define CMARK_OPT_NORMALIZE (1 << 8)
|
||||
|
||||
/** Validate UTF-8 in the input before parsing, replacing illegal
|
||||
* sequences with the replacement character U+FFFD.
|
||||
*/
|
||||
#define CMARK_OPT_VALIDATE_UTF8 (1 << 9)
|
||||
|
||||
/** Convert straight quotes to curly, --- to em dashes, -- to en dashes.
|
||||
*/
|
||||
#define CMARK_OPT_SMART (1 << 10)
|
||||
|
||||
/**
|
||||
* ## Version information
|
||||
*/
|
||||
|
||||
/** The library version as integer for runtime checks. Also available as
|
||||
* macro CMARK_VERSION for compile time checks.
|
||||
*
|
||||
* * Bits 16-23 contain the major version.
|
||||
* * Bits 8-15 contain the minor version.
|
||||
* * Bits 0-7 contain the patchlevel.
|
||||
*
|
||||
* In hexadecimal format, the number 0x010203 represents version 1.2.3.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
int cmark_version(void);
|
||||
|
||||
/** The library version string for runtime checks. Also available as
|
||||
* macro CMARK_VERSION_STRING for compile time checks.
|
||||
*/
|
||||
CMARK_EXPORT
|
||||
const char *cmark_version_string(void);
|
||||
|
||||
/** # AUTHORS
|
||||
*
|
||||
* John MacFarlane, Vicent Marti, Kārlis Gaņģis, Nick Wellnhofer.
|
||||
*/
|
||||
|
||||
#ifndef CMARK_NO_SHORT_NAMES
|
||||
#define NODE_DOCUMENT CMARK_NODE_DOCUMENT
|
||||
#define NODE_BLOCK_QUOTE CMARK_NODE_BLOCK_QUOTE
|
||||
#define NODE_LIST CMARK_NODE_LIST
|
||||
#define NODE_ITEM CMARK_NODE_ITEM
|
||||
#define NODE_CODE_BLOCK CMARK_NODE_CODE_BLOCK
|
||||
#define NODE_HTML_BLOCK CMARK_NODE_HTML_BLOCK
|
||||
#define NODE_CUSTOM_BLOCK CMARK_NODE_CUSTOM_BLOCK
|
||||
#define NODE_PARAGRAPH CMARK_NODE_PARAGRAPH
|
||||
#define NODE_HEADING CMARK_NODE_HEADING
|
||||
#define NODE_HEADER CMARK_NODE_HEADER
|
||||
#define NODE_THEMATIC_BREAK CMARK_NODE_THEMATIC_BREAK
|
||||
#define NODE_HRULE CMARK_NODE_HRULE
|
||||
#define NODE_TEXT CMARK_NODE_TEXT
|
||||
#define NODE_SOFTBREAK CMARK_NODE_SOFTBREAK
|
||||
#define NODE_LINEBREAK CMARK_NODE_LINEBREAK
|
||||
#define NODE_CODE CMARK_NODE_CODE
|
||||
#define NODE_HTML_INLINE CMARK_NODE_HTML_INLINE
|
||||
#define NODE_CUSTOM_INLINE CMARK_NODE_CUSTOM_INLINE
|
||||
#define NODE_EMPH CMARK_NODE_EMPH
|
||||
#define NODE_STRONG CMARK_NODE_STRONG
|
||||
#define NODE_LINK CMARK_NODE_LINK
|
||||
#define NODE_IMAGE CMARK_NODE_IMAGE
|
||||
#define BULLET_LIST CMARK_BULLET_LIST
|
||||
#define ORDERED_LIST CMARK_ORDERED_LIST
|
||||
#define PERIOD_DELIM CMARK_PERIOD_DELIM
|
||||
#define PAREN_DELIM CMARK_PAREN_DELIM
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,44 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include "cmark_ctype.h"
|
||||
|
||||
/** 1 = space, 2 = punct, 3 = digit, 4 = alpha, 0 = other
|
||||
*/
|
||||
static const uint8_t cmark_ctype_class[256] = {
|
||||
/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
|
||||
/* 0 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0,
|
||||
/* 1 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 2 */ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
/* 3 */ 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2,
|
||||
/* 4 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
/* 5 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 2,
|
||||
/* 6 */ 2, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
|
||||
/* 7 */ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 2, 2, 0,
|
||||
/* 8 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* 9 */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* a */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* b */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* c */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* d */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* e */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
/* f */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
/**
|
||||
* Returns 1 if c is a "whitespace" character as defined by the spec.
|
||||
*/
|
||||
int cmark_isspace(char c) { return cmark_ctype_class[(uint8_t)c] == 1; }
|
||||
|
||||
/**
|
||||
* Returns 1 if c is an ascii punctuation character.
|
||||
*/
|
||||
int cmark_ispunct(char c) { return cmark_ctype_class[(uint8_t)c] == 2; }
|
||||
|
||||
int cmark_isalnum(char c) {
|
||||
uint8_t result;
|
||||
result = cmark_ctype_class[(uint8_t)c];
|
||||
return (result == 3 || result == 4);
|
||||
}
|
||||
|
||||
int cmark_isdigit(char c) { return cmark_ctype_class[(uint8_t)c] == 3; }
|
||||
|
||||
int cmark_isalpha(char c) { return cmark_ctype_class[(uint8_t)c] == 4; }
|
|
@ -0,0 +1,26 @@
|
|||
#ifndef CMARK_CMARK_CTYPE_H
|
||||
#define CMARK_CMARK_CTYPE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Locale-independent versions of functions from ctype.h.
|
||||
* We want cmark to behave the same no matter what the system locale.
|
||||
*/
|
||||
|
||||
int cmark_isspace(char c);
|
||||
|
||||
int cmark_ispunct(char c);
|
||||
|
||||
int cmark_isalnum(char c);
|
||||
|
||||
int cmark_isdigit(char c);
|
||||
|
||||
int cmark_isalpha(char c);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
#ifndef CMARK_EXPORT_H
|
||||
#define CMARK_EXPORT_H
|
||||
|
||||
#ifdef CMARK_STATIC_DEFINE
|
||||
# define CMARK_EXPORT
|
||||
# define CMARK_NO_EXPORT
|
||||
#else
|
||||
# ifndef CMARK_EXPORT
|
||||
# ifdef libcmark_EXPORTS
|
||||
/* We are building this library */
|
||||
# define CMARK_EXPORT __attribute__((visibility("default")))
|
||||
# else
|
||||
/* We are using this library */
|
||||
# define CMARK_EXPORT __attribute__((visibility("default")))
|
||||
# endif
|
||||
# endif
|
||||
|
||||
# ifndef CMARK_NO_EXPORT
|
||||
# define CMARK_NO_EXPORT __attribute__((visibility("hidden")))
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef CMARK_DEPRECATED
|
||||
# define CMARK_DEPRECATED __attribute__ ((__deprecated__))
|
||||
#endif
|
||||
|
||||
#ifndef CMARK_DEPRECATED_EXPORT
|
||||
# define CMARK_DEPRECATED_EXPORT CMARK_EXPORT CMARK_DEPRECATED
|
||||
#endif
|
||||
|
||||
#ifndef CMARK_DEPRECATED_NO_EXPORT
|
||||
# define CMARK_DEPRECATED_NO_EXPORT CMARK_NO_EXPORT CMARK_DEPRECATED
|
||||
#endif
|
||||
|
||||
#if 0 /* DEFINE_NO_DEPRECATED */
|
||||
# ifndef CMARK_NO_DEPRECATED
|
||||
# define CMARK_NO_DEPRECATED
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif /* CMARK_EXPORT_H */
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef CMARK_VERSION_H
|
||||
#define CMARK_VERSION_H
|
||||
|
||||
#define CMARK_VERSION ((0 << 16) | (28 << 8) | 3)
|
||||
#define CMARK_VERSION_STRING "0.28.3"
|
||||
|
||||
#endif
|
|
@ -0,0 +1,481 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "cmark.h"
|
||||
#include "node.h"
|
||||
#include "buffer.h"
|
||||
#include "utf8.h"
|
||||
#include "scanners.h"
|
||||
#include "render.h"
|
||||
|
||||
#define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping)
|
||||
#define LIT(s) renderer->out(renderer, s, false, LITERAL)
|
||||
#define CR() renderer->cr(renderer)
|
||||
#define BLANKLINE() renderer->blankline(renderer)
|
||||
#define ENCODED_SIZE 20
|
||||
#define LISTMARKER_SIZE 20
|
||||
|
||||
// Functions to convert cmark_nodes to commonmark strings.
|
||||
|
||||
static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_escaping escape,
|
||||
int32_t c, unsigned char nextc) {
|
||||
bool needs_escaping = false;
|
||||
bool follows_digit =
|
||||
renderer->buffer->size > 0 &&
|
||||
cmark_isdigit(renderer->buffer->ptr[renderer->buffer->size - 1]);
|
||||
char encoded[ENCODED_SIZE];
|
||||
|
||||
needs_escaping =
|
||||
c < 0x80 && escape != LITERAL &&
|
||||
((escape == NORMAL &&
|
||||
(c == '*' || c == '_' || c == '[' || c == ']' || c == '#' || c == '<' ||
|
||||
c == '>' || c == '\\' || c == '`' || c == '!' ||
|
||||
(c == '&' && cmark_isalpha(nextc)) || (c == '!' && nextc == '[') ||
|
||||
(renderer->begin_content && (c == '-' || c == '+' || c == '=') &&
|
||||
// begin_content doesn't get set to false til we've passed digits
|
||||
// at the beginning of line, so...
|
||||
!follows_digit) ||
|
||||
(renderer->begin_content && (c == '.' || c == ')') && follows_digit &&
|
||||
(nextc == 0 || cmark_isspace(nextc))))) ||
|
||||
(escape == URL &&
|
||||
(c == '`' || c == '<' || c == '>' || cmark_isspace(c) || c == '\\' ||
|
||||
c == ')' || c == '(')) ||
|
||||
(escape == TITLE &&
|
||||
(c == '`' || c == '<' || c == '>' || c == '"' || c == '\\')));
|
||||
|
||||
if (needs_escaping) {
|
||||
if (cmark_isspace(c)) {
|
||||
// use percent encoding for spaces
|
||||
snprintf(encoded, ENCODED_SIZE, "%%%2x", c);
|
||||
cmark_strbuf_puts(renderer->buffer, encoded);
|
||||
renderer->column += 3;
|
||||
} else {
|
||||
cmark_render_ascii(renderer, "\\");
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
}
|
||||
|
||||
static int longest_backtick_sequence(const char *code) {
|
||||
int longest = 0;
|
||||
int current = 0;
|
||||
size_t i = 0;
|
||||
size_t code_len = strlen(code);
|
||||
while (i <= code_len) {
|
||||
if (code[i] == '`') {
|
||||
current++;
|
||||
} else {
|
||||
if (current > longest) {
|
||||
longest = current;
|
||||
}
|
||||
current = 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return longest;
|
||||
}
|
||||
|
||||
static int shortest_unused_backtick_sequence(const char *code) {
|
||||
// note: if the shortest sequence is >= 32, this returns 32
|
||||
// so as not to overflow the bit array.
|
||||
uint32_t used = 1;
|
||||
int current = 0;
|
||||
size_t i = 0;
|
||||
size_t code_len = strlen(code);
|
||||
while (i <= code_len) {
|
||||
if (code[i] == '`') {
|
||||
current++;
|
||||
} else {
|
||||
if (current > 0 && current < 32) {
|
||||
used |= (1U << current);
|
||||
}
|
||||
current = 0;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
// return number of first bit that is 0:
|
||||
i = 0;
|
||||
while (i < 32 && used & 1) {
|
||||
used = used >> 1;
|
||||
i++;
|
||||
}
|
||||
return (int)i;
|
||||
}
|
||||
|
||||
static bool is_autolink(cmark_node *node) {
|
||||
cmark_chunk *title;
|
||||
cmark_chunk *url;
|
||||
cmark_node *link_text;
|
||||
char *realurl;
|
||||
int realurllen;
|
||||
|
||||
if (node->type != CMARK_NODE_LINK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
url = &node->as.link.url;
|
||||
if (url->len == 0 || scan_scheme(url, 0) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
title = &node->as.link.title;
|
||||
// if it has a title, we can't treat it as an autolink:
|
||||
if (title->len > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
link_text = node->first_child;
|
||||
if (link_text == NULL) {
|
||||
return false;
|
||||
}
|
||||
cmark_consolidate_text_nodes(link_text);
|
||||
realurl = (char *)url->data;
|
||||
realurllen = url->len;
|
||||
if (strncmp(realurl, "mailto:", 7) == 0) {
|
||||
realurl += 7;
|
||||
realurllen -= 7;
|
||||
}
|
||||
return (realurllen == link_text->as.literal.len &&
|
||||
strncmp(realurl, (char *)link_text->as.literal.data,
|
||||
link_text->as.literal.len) == 0);
|
||||
}
|
||||
|
||||
// if node is a block node, returns node.
|
||||
// otherwise returns first block-level node that is an ancestor of node.
|
||||
// if there is no block-level ancestor, returns NULL.
|
||||
static cmark_node *get_containing_block(cmark_node *node) {
|
||||
while (node) {
|
||||
if (node->type >= CMARK_NODE_FIRST_BLOCK &&
|
||||
node->type <= CMARK_NODE_LAST_BLOCK) {
|
||||
return node;
|
||||
} else {
|
||||
node = node->parent;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int S_render_node(cmark_renderer *renderer, cmark_node *node,
|
||||
cmark_event_type ev_type, int options) {
|
||||
cmark_node *tmp;
|
||||
int list_number;
|
||||
cmark_delim_type list_delim;
|
||||
int numticks;
|
||||
bool extra_spaces;
|
||||
int i;
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
const char *info, *code, *title;
|
||||
char fencechar[2] = {'\0', '\0'};
|
||||
size_t info_len, code_len;
|
||||
char listmarker[LISTMARKER_SIZE];
|
||||
char *emph_delim;
|
||||
bool first_in_list_item;
|
||||
bufsize_t marker_width;
|
||||
bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options) &&
|
||||
!(CMARK_OPT_HARDBREAKS & options);
|
||||
|
||||
// Don't adjust tight list status til we've started the list.
|
||||
// Otherwise we loose the blank line between a paragraph and
|
||||
// a following list.
|
||||
if (!(node->type == CMARK_NODE_ITEM && node->prev == NULL && entering)) {
|
||||
tmp = get_containing_block(node);
|
||||
renderer->in_tight_list_item =
|
||||
tmp && // tmp might be NULL if there is no containing block
|
||||
((tmp->type == CMARK_NODE_ITEM &&
|
||||
cmark_node_get_list_tight(tmp->parent)) ||
|
||||
(tmp && tmp->parent && tmp->parent->type == CMARK_NODE_ITEM &&
|
||||
cmark_node_get_list_tight(tmp->parent->parent)));
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_DOCUMENT:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_BLOCK_QUOTE:
|
||||
if (entering) {
|
||||
LIT("> ");
|
||||
renderer->begin_content = true;
|
||||
cmark_strbuf_puts(renderer->prefix, "> ");
|
||||
} else {
|
||||
cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 2);
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LIST:
|
||||
if (!entering && node->next && (node->next->type == CMARK_NODE_CODE_BLOCK ||
|
||||
node->next->type == CMARK_NODE_LIST)) {
|
||||
// this ensures that a following indented code block or list will be
|
||||
// inteprereted correctly.
|
||||
CR();
|
||||
LIT("<!-- end list -->");
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_ITEM:
|
||||
if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) {
|
||||
marker_width = 4;
|
||||
} else {
|
||||
list_number = cmark_node_get_list_start(node->parent);
|
||||
list_delim = cmark_node_get_list_delim(node->parent);
|
||||
tmp = node;
|
||||
while (tmp->prev) {
|
||||
tmp = tmp->prev;
|
||||
list_number += 1;
|
||||
}
|
||||
// we ensure a width of at least 4 so
|
||||
// we get nice transition from single digits
|
||||
// to double
|
||||
snprintf(listmarker, LISTMARKER_SIZE, "%d%s%s", list_number,
|
||||
list_delim == CMARK_PAREN_DELIM ? ")" : ".",
|
||||
list_number < 10 ? " " : " ");
|
||||
marker_width = strlen(listmarker);
|
||||
}
|
||||
if (entering) {
|
||||
if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) {
|
||||
LIT(" - ");
|
||||
renderer->begin_content = true;
|
||||
} else {
|
||||
LIT(listmarker);
|
||||
renderer->begin_content = true;
|
||||
}
|
||||
for (i = marker_width; i--;) {
|
||||
cmark_strbuf_putc(renderer->prefix, ' ');
|
||||
}
|
||||
} else {
|
||||
cmark_strbuf_truncate(renderer->prefix,
|
||||
renderer->prefix->size - marker_width);
|
||||
CR();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HEADING:
|
||||
if (entering) {
|
||||
for (i = cmark_node_get_heading_level(node); i > 0; i--) {
|
||||
LIT("#");
|
||||
}
|
||||
LIT(" ");
|
||||
renderer->begin_content = true;
|
||||
renderer->no_linebreaks = true;
|
||||
} else {
|
||||
renderer->no_linebreaks = false;
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
first_in_list_item = node->prev == NULL && node->parent &&
|
||||
node->parent->type == CMARK_NODE_ITEM;
|
||||
|
||||
if (!first_in_list_item) {
|
||||
BLANKLINE();
|
||||
}
|
||||
info = cmark_node_get_fence_info(node);
|
||||
info_len = strlen(info);
|
||||
fencechar[0] = strchr(info, '`') == NULL ? '`' : '~';
|
||||
code = cmark_node_get_literal(node);
|
||||
code_len = strlen(code);
|
||||
// use indented form if no info, and code doesn't
|
||||
// begin or end with a blank line, and code isn't
|
||||
// first thing in a list item
|
||||
if (info_len == 0 && (code_len > 2 && !cmark_isspace(code[0]) &&
|
||||
!(cmark_isspace(code[code_len - 1]) &&
|
||||
cmark_isspace(code[code_len - 2]))) &&
|
||||
!first_in_list_item) {
|
||||
LIT(" ");
|
||||
cmark_strbuf_puts(renderer->prefix, " ");
|
||||
OUT(cmark_node_get_literal(node), false, LITERAL);
|
||||
cmark_strbuf_truncate(renderer->prefix, renderer->prefix->size - 4);
|
||||
} else {
|
||||
numticks = longest_backtick_sequence(code) + 1;
|
||||
if (numticks < 3) {
|
||||
numticks = 3;
|
||||
}
|
||||
for (i = 0; i < numticks; i++) {
|
||||
LIT(fencechar);
|
||||
}
|
||||
LIT(" ");
|
||||
OUT(info, false, LITERAL);
|
||||
CR();
|
||||
OUT(cmark_node_get_literal(node), false, LITERAL);
|
||||
CR();
|
||||
for (i = 0; i < numticks; i++) {
|
||||
LIT(fencechar);
|
||||
}
|
||||
}
|
||||
BLANKLINE();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
BLANKLINE();
|
||||
OUT(cmark_node_get_literal(node), false, LITERAL);
|
||||
BLANKLINE();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
BLANKLINE();
|
||||
OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
|
||||
false, LITERAL);
|
||||
BLANKLINE();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_THEMATIC_BREAK:
|
||||
BLANKLINE();
|
||||
LIT("-----");
|
||||
BLANKLINE();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
if (!entering) {
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_TEXT:
|
||||
OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINEBREAK:
|
||||
if (!(CMARK_OPT_HARDBREAKS & options)) {
|
||||
LIT(" ");
|
||||
}
|
||||
CR();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_SOFTBREAK:
|
||||
if (CMARK_OPT_HARDBREAKS & options) {
|
||||
LIT(" ");
|
||||
CR();
|
||||
} else if (!renderer->no_linebreaks && renderer->width == 0 &&
|
||||
!(CMARK_OPT_HARDBREAKS & options) &&
|
||||
!(CMARK_OPT_NOBREAKS & options)) {
|
||||
CR();
|
||||
} else {
|
||||
OUT(" ", allow_wrap, LITERAL);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE:
|
||||
code = cmark_node_get_literal(node);
|
||||
code_len = strlen(code);
|
||||
numticks = shortest_unused_backtick_sequence(code);
|
||||
extra_spaces = code_len == 0 ||
|
||||
code[0] == '`' || code[code_len - 1] == '`' ||
|
||||
code[0] == ' ' || code[code_len - 1] == ' ';
|
||||
for (i = 0; i < numticks; i++) {
|
||||
LIT("`");
|
||||
}
|
||||
if (extra_spaces) {
|
||||
LIT(" ");
|
||||
}
|
||||
OUT(cmark_node_get_literal(node), allow_wrap, LITERAL);
|
||||
if (extra_spaces) {
|
||||
LIT(" ");
|
||||
}
|
||||
for (i = 0; i < numticks; i++) {
|
||||
LIT("`");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
OUT(cmark_node_get_literal(node), false, LITERAL);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
|
||||
false, LITERAL);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_STRONG:
|
||||
if (entering) {
|
||||
LIT("**");
|
||||
} else {
|
||||
LIT("**");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_EMPH:
|
||||
// If we have EMPH(EMPH(x)), we need to use *_x_*
|
||||
// because **x** is STRONG(x):
|
||||
if (node->parent && node->parent->type == CMARK_NODE_EMPH &&
|
||||
node->next == NULL && node->prev == NULL) {
|
||||
emph_delim = "_";
|
||||
} else {
|
||||
emph_delim = "*";
|
||||
}
|
||||
if (entering) {
|
||||
LIT(emph_delim);
|
||||
} else {
|
||||
LIT(emph_delim);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINK:
|
||||
if (is_autolink(node)) {
|
||||
if (entering) {
|
||||
LIT("<");
|
||||
if (strncmp(cmark_node_get_url(node), "mailto:", 7) == 0) {
|
||||
LIT((const char *)cmark_node_get_url(node) + 7);
|
||||
} else {
|
||||
LIT((const char *)cmark_node_get_url(node));
|
||||
}
|
||||
LIT(">");
|
||||
// return signal to skip contents of node...
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
if (entering) {
|
||||
LIT("[");
|
||||
} else {
|
||||
LIT("](");
|
||||
OUT(cmark_node_get_url(node), false, URL);
|
||||
title = cmark_node_get_title(node);
|
||||
if (strlen(title) > 0) {
|
||||
LIT(" \"");
|
||||
OUT(title, false, TITLE);
|
||||
LIT("\"");
|
||||
}
|
||||
LIT(")");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_IMAGE:
|
||||
if (entering) {
|
||||
LIT("![");
|
||||
} else {
|
||||
LIT("](");
|
||||
OUT(cmark_node_get_url(node), false, URL);
|
||||
title = cmark_node_get_title(node);
|
||||
if (strlen(title) > 0) {
|
||||
OUT(" \"", allow_wrap, LITERAL);
|
||||
OUT(title, false, TITLE);
|
||||
LIT("\"");
|
||||
}
|
||||
LIT(")");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *cmark_render_commonmark(cmark_node *root, int options, int width) {
|
||||
if (options & CMARK_OPT_HARDBREAKS) {
|
||||
// disable breaking on width, since it has
|
||||
// a different meaning with OPT_HARDBREAKS
|
||||
width = 0;
|
||||
}
|
||||
return cmark_render(root, options, width, outc, S_render_node);
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef CMARK_CONFIG_H
|
||||
#define CMARK_CONFIG_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define HAVE_STDBOOL_H
|
||||
|
||||
#ifdef HAVE_STDBOOL_H
|
||||
#include <stdbool.h>
|
||||
#elif !defined(__cplusplus)
|
||||
typedef char bool;
|
||||
#endif
|
||||
|
||||
#define HAVE___BUILTIN_EXPECT
|
||||
|
||||
#define HAVE___ATTRIBUTE__
|
||||
|
||||
#ifdef HAVE___ATTRIBUTE__
|
||||
#define CMARK_ATTRIBUTE(list) __attribute__ (list)
|
||||
#else
|
||||
#define CMARK_ATTRIBUTE(list)
|
||||
#endif
|
||||
|
||||
#ifndef CMARK_INLINE
|
||||
#if defined(_MSC_VER) && !defined(__cplusplus)
|
||||
#define CMARK_INLINE __inline
|
||||
#else
|
||||
#define CMARK_INLINE inline
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* snprintf and vsnprintf fallbacks for MSVC before 2015,
|
||||
due to Valentin Milea http://stackoverflow.com/questions/2915672/
|
||||
*/
|
||||
|
||||
#if defined(_MSC_VER) && _MSC_VER < 1900
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#define snprintf c99_snprintf
|
||||
#define vsnprintf c99_vsnprintf
|
||||
|
||||
CMARK_INLINE int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap)
|
||||
{
|
||||
int count = -1;
|
||||
|
||||
if (size != 0)
|
||||
count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap);
|
||||
if (count == -1)
|
||||
count = _vscprintf(format, ap);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
CMARK_INLINE int c99_snprintf(char *outBuf, size_t size, const char *format, ...)
|
||||
{
|
||||
int count;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, format);
|
||||
count = c99_vsnprintf(outBuf, size, format, ap);
|
||||
va_end(ap);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,51 @@
|
|||
#ifndef CMARK_HOUDINI_H
|
||||
#define CMARK_HOUDINI_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#include "config.h"
|
||||
#include "buffer.h"
|
||||
|
||||
#ifdef HAVE___BUILTIN_EXPECT
|
||||
#define likely(x) __builtin_expect((x), 1)
|
||||
#define unlikely(x) __builtin_expect((x), 0)
|
||||
#else
|
||||
#define likely(x) (x)
|
||||
#define unlikely(x) (x)
|
||||
#endif
|
||||
|
||||
#ifdef HOUDINI_USE_LOCALE
|
||||
#define _isxdigit(c) isxdigit(c)
|
||||
#define _isdigit(c) isdigit(c)
|
||||
#else
|
||||
/*
|
||||
* Helper _isdigit methods -- do not trust the current locale
|
||||
* */
|
||||
#define _isxdigit(c) (strchr("0123456789ABCDEFabcdef", (c)) != NULL)
|
||||
#define _isdigit(c) ((c) >= '0' && (c) <= '9')
|
||||
#endif
|
||||
|
||||
#define HOUDINI_ESCAPED_SIZE(x) (((x)*12) / 10)
|
||||
#define HOUDINI_UNESCAPED_SIZE(x) (x)
|
||||
|
||||
extern bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size);
|
||||
extern int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size);
|
||||
extern int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size, int secure);
|
||||
extern int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size);
|
||||
extern void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size);
|
||||
extern int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,100 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "houdini.h"
|
||||
|
||||
/*
|
||||
* 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 char HREF_SAFE[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 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,
|
||||
};
|
||||
|
||||
int houdini_escape_href(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) {
|
||||
static const uint8_t hex_chars[] = "0123456789ABCDEF";
|
||||
bufsize_t i = 0, org;
|
||||
uint8_t hex_str[3];
|
||||
|
||||
hex_str[0] = '%';
|
||||
|
||||
while (i < size) {
|
||||
org = i;
|
||||
while (i < size && HREF_SAFE[src[i]] != 0)
|
||||
i++;
|
||||
|
||||
if (likely(i > org))
|
||||
cmark_strbuf_put(ob, src + org, i - org);
|
||||
|
||||
/* escaping */
|
||||
if (i >= size)
|
||||
break;
|
||||
|
||||
switch (src[i]) {
|
||||
/* amp appears all the time in URLs, but needs
|
||||
* HTML-entity escaping to be inside an href */
|
||||
case '&':
|
||||
cmark_strbuf_puts(ob, "&");
|
||||
break;
|
||||
|
||||
/* the single quote is a valid URL character
|
||||
* according to the standard; it needs HTML
|
||||
* entity escaping too */
|
||||
case '\'':
|
||||
cmark_strbuf_puts(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 ' ':
|
||||
cmark_strbuf_putc(ob, '+');
|
||||
break;
|
||||
#endif
|
||||
|
||||
/* every other character goes with a %XX escaping */
|
||||
default:
|
||||
hex_str[1] = hex_chars[(src[i] >> 4) & 0xF];
|
||||
hex_str[2] = hex_chars[src[i] & 0xF];
|
||||
cmark_strbuf_put(ob, hex_str, 3);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "houdini.h"
|
||||
|
||||
/**
|
||||
* According to the OWASP rules:
|
||||
*
|
||||
* & --> &
|
||||
* < --> <
|
||||
* > --> >
|
||||
* " --> "
|
||||
* ' --> ' ' is not recommended
|
||||
* / --> / forward slash is included as it helps end an HTML entity
|
||||
*
|
||||
*/
|
||||
static const char HTML_ESCAPE_TABLE[] = {
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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[] = {"", """, "&", "'",
|
||||
"/", "<", ">"};
|
||||
|
||||
int houdini_escape_html0(cmark_strbuf *ob, const uint8_t *src, bufsize_t size,
|
||||
int secure) {
|
||||
bufsize_t i = 0, org, esc = 0;
|
||||
|
||||
while (i < size) {
|
||||
org = i;
|
||||
while (i < size && (esc = HTML_ESCAPE_TABLE[src[i]]) == 0)
|
||||
i++;
|
||||
|
||||
if (i > org)
|
||||
cmark_strbuf_put(ob, src + org, i - org);
|
||||
|
||||
/* escaping */
|
||||
if (unlikely(i >= size))
|
||||
break;
|
||||
|
||||
/* The forward slash is only escaped in secure mode */
|
||||
if ((src[i] == '/' || src[i] == '\'') && !secure) {
|
||||
cmark_strbuf_putc(ob, src[i]);
|
||||
} else {
|
||||
cmark_strbuf_puts(ob, HTML_ESCAPES[esc]);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int houdini_escape_html(cmark_strbuf *ob, const uint8_t *src, bufsize_t size) {
|
||||
return houdini_escape_html0(ob, src, size, 1);
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "buffer.h"
|
||||
#include "houdini.h"
|
||||
#include "utf8.h"
|
||||
#include "entities.inc"
|
||||
|
||||
/* Binary tree lookup code for entities added by JGM */
|
||||
|
||||
static const unsigned char *S_lookup(int i, int low, int hi,
|
||||
const unsigned char *s, int len) {
|
||||
int j;
|
||||
int cmp =
|
||||
strncmp((const char *)s, (const char *)cmark_entities[i].entity, len);
|
||||
if (cmp == 0 && cmark_entities[i].entity[len] == 0) {
|
||||
return (const unsigned char *)cmark_entities[i].bytes;
|
||||
} else if (cmp <= 0 && i > low) {
|
||||
j = i - ((i - low) / 2);
|
||||
if (j == i)
|
||||
j -= 1;
|
||||
return S_lookup(j, low, i - 1, s, len);
|
||||
} else if (cmp > 0 && i < hi) {
|
||||
j = i + ((hi - i) / 2);
|
||||
if (j == i)
|
||||
j += 1;
|
||||
return S_lookup(j, i + 1, hi, s, len);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const unsigned char *S_lookup_entity(const unsigned char *s, int len) {
|
||||
return S_lookup(CMARK_NUM_ENTITIES / 2, 0, CMARK_NUM_ENTITIES - 1, s, len);
|
||||
}
|
||||
|
||||
bufsize_t houdini_unescape_ent(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size) {
|
||||
bufsize_t i = 0;
|
||||
|
||||
if (size >= 3 && src[0] == '#') {
|
||||
int codepoint = 0;
|
||||
int num_digits = 0;
|
||||
|
||||
if (_isdigit(src[1])) {
|
||||
for (i = 1; i < size && _isdigit(src[i]); ++i) {
|
||||
codepoint = (codepoint * 10) + (src[i] - '0');
|
||||
|
||||
if (codepoint >= 0x110000) {
|
||||
// Keep counting digits but
|
||||
// avoid integer overflow.
|
||||
codepoint = 0x110000;
|
||||
}
|
||||
}
|
||||
|
||||
num_digits = i - 1;
|
||||
}
|
||||
|
||||
else if (src[1] == 'x' || src[1] == 'X') {
|
||||
for (i = 2; i < size && _isxdigit(src[i]); ++i) {
|
||||
codepoint = (codepoint * 16) + ((src[i] | 32) % 39 - 9);
|
||||
|
||||
if (codepoint >= 0x110000) {
|
||||
// Keep counting digits but
|
||||
// avoid integer overflow.
|
||||
codepoint = 0x110000;
|
||||
}
|
||||
}
|
||||
|
||||
num_digits = i - 2;
|
||||
}
|
||||
|
||||
if (num_digits >= 1 && num_digits <= 8 && i < size && src[i] == ';') {
|
||||
if (codepoint == 0 || (codepoint >= 0xD800 && codepoint < 0xE000) ||
|
||||
codepoint >= 0x110000) {
|
||||
codepoint = 0xFFFD;
|
||||
}
|
||||
cmark_utf8proc_encode_char(codepoint, ob);
|
||||
return i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
else {
|
||||
if (size > CMARK_ENTITY_MAX_LENGTH)
|
||||
size = CMARK_ENTITY_MAX_LENGTH;
|
||||
|
||||
for (i = CMARK_ENTITY_MIN_LENGTH; i < size; ++i) {
|
||||
if (src[i] == ' ')
|
||||
break;
|
||||
|
||||
if (src[i] == ';') {
|
||||
const unsigned char *entity = S_lookup_entity(src, i);
|
||||
|
||||
if (entity != NULL) {
|
||||
cmark_strbuf_puts(ob, (const char *)entity);
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int houdini_unescape_html(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size) {
|
||||
bufsize_t i = 0, org, ent;
|
||||
|
||||
while (i < size) {
|
||||
org = i;
|
||||
while (i < size && src[i] != '&')
|
||||
i++;
|
||||
|
||||
if (likely(i > org)) {
|
||||
if (unlikely(org == 0)) {
|
||||
if (i >= size)
|
||||
return 0;
|
||||
|
||||
cmark_strbuf_grow(ob, HOUDINI_UNESCAPED_SIZE(size));
|
||||
}
|
||||
|
||||
cmark_strbuf_put(ob, src + org, i - org);
|
||||
}
|
||||
|
||||
/* escaping */
|
||||
if (i >= size)
|
||||
break;
|
||||
|
||||
i++;
|
||||
|
||||
ent = houdini_unescape_ent(ob, src + i, size - i);
|
||||
i += ent;
|
||||
|
||||
/* not really an entity */
|
||||
if (ent == 0)
|
||||
cmark_strbuf_putc(ob, '&');
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void houdini_unescape_html_f(cmark_strbuf *ob, const uint8_t *src,
|
||||
bufsize_t size) {
|
||||
if (!houdini_unescape_html(ob, src, size))
|
||||
cmark_strbuf_put(ob, src, size);
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include "cmark_ctype.h"
|
||||
#include "config.h"
|
||||
#include "cmark.h"
|
||||
#include "node.h"
|
||||
#include "buffer.h"
|
||||
#include "houdini.h"
|
||||
#include "scanners.h"
|
||||
|
||||
#define BUFFER_SIZE 100
|
||||
|
||||
// Functions to convert cmark_nodes to HTML strings.
|
||||
|
||||
static void escape_html(cmark_strbuf *dest, const unsigned char *source,
|
||||
bufsize_t length) {
|
||||
houdini_escape_html0(dest, source, length, 0);
|
||||
}
|
||||
|
||||
static CMARK_INLINE void cr(cmark_strbuf *html) {
|
||||
if (html->size && html->ptr[html->size - 1] != '\n')
|
||||
cmark_strbuf_putc(html, '\n');
|
||||
}
|
||||
|
||||
struct render_state {
|
||||
cmark_strbuf *html;
|
||||
cmark_node *plain;
|
||||
};
|
||||
|
||||
static void S_render_sourcepos(cmark_node *node, cmark_strbuf *html,
|
||||
int options) {
|
||||
char buffer[BUFFER_SIZE];
|
||||
if (CMARK_OPT_SOURCEPOS & options) {
|
||||
snprintf(buffer, BUFFER_SIZE, " data-sourcepos=\"%d:%d-%d:%d\"",
|
||||
cmark_node_get_start_line(node), cmark_node_get_start_column(node),
|
||||
cmark_node_get_end_line(node), cmark_node_get_end_column(node));
|
||||
cmark_strbuf_puts(html, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
static int S_render_node(cmark_node *node, cmark_event_type ev_type,
|
||||
struct render_state *state, int options) {
|
||||
cmark_node *parent;
|
||||
cmark_node *grandparent;
|
||||
cmark_strbuf *html = state->html;
|
||||
char start_heading[] = "<h0";
|
||||
char end_heading[] = "</h0";
|
||||
bool tight;
|
||||
char buffer[BUFFER_SIZE];
|
||||
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
|
||||
if (state->plain == node) { // back at original node
|
||||
state->plain = NULL;
|
||||
}
|
||||
|
||||
if (state->plain != NULL) {
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_TEXT:
|
||||
case CMARK_NODE_CODE:
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
escape_html(html, node->as.literal.data, node->as.literal.len);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINEBREAK:
|
||||
case CMARK_NODE_SOFTBREAK:
|
||||
cmark_strbuf_putc(html, ' ');
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_DOCUMENT:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_BLOCK_QUOTE:
|
||||
if (entering) {
|
||||
cr(html);
|
||||
cmark_strbuf_puts(html, "<blockquote");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_puts(html, ">\n");
|
||||
} else {
|
||||
cr(html);
|
||||
cmark_strbuf_puts(html, "</blockquote>\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LIST: {
|
||||
cmark_list_type list_type = node->as.list.list_type;
|
||||
int start = node->as.list.start;
|
||||
|
||||
if (entering) {
|
||||
cr(html);
|
||||
if (list_type == CMARK_BULLET_LIST) {
|
||||
cmark_strbuf_puts(html, "<ul");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_puts(html, ">\n");
|
||||
} else if (start == 1) {
|
||||
cmark_strbuf_puts(html, "<ol");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_puts(html, ">\n");
|
||||
} else {
|
||||
snprintf(buffer, BUFFER_SIZE, "<ol start=\"%d\"", start);
|
||||
cmark_strbuf_puts(html, buffer);
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_puts(html, ">\n");
|
||||
}
|
||||
} else {
|
||||
cmark_strbuf_puts(html,
|
||||
list_type == CMARK_BULLET_LIST ? "</ul>\n" : "</ol>\n");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case CMARK_NODE_ITEM:
|
||||
if (entering) {
|
||||
cr(html);
|
||||
cmark_strbuf_puts(html, "<li");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_putc(html, '>');
|
||||
} else {
|
||||
cmark_strbuf_puts(html, "</li>\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HEADING:
|
||||
if (entering) {
|
||||
cr(html);
|
||||
start_heading[2] = (char)('0' + node->as.heading.level);
|
||||
cmark_strbuf_puts(html, start_heading);
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_putc(html, '>');
|
||||
} else {
|
||||
end_heading[3] = (char)('0' + node->as.heading.level);
|
||||
cmark_strbuf_puts(html, end_heading);
|
||||
cmark_strbuf_puts(html, ">\n");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
cr(html);
|
||||
|
||||
if (node->as.code.info.len == 0) {
|
||||
cmark_strbuf_puts(html, "<pre");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_puts(html, "><code>");
|
||||
} else {
|
||||
bufsize_t first_tag = 0;
|
||||
while (first_tag < node->as.code.info.len &&
|
||||
!cmark_isspace(node->as.code.info.data[first_tag])) {
|
||||
first_tag += 1;
|
||||
}
|
||||
|
||||
cmark_strbuf_puts(html, "<pre");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_puts(html, "><code class=\"language-");
|
||||
escape_html(html, node->as.code.info.data, first_tag);
|
||||
cmark_strbuf_puts(html, "\">");
|
||||
}
|
||||
|
||||
escape_html(html, node->as.code.literal.data, node->as.code.literal.len);
|
||||
cmark_strbuf_puts(html, "</code></pre>\n");
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
cr(html);
|
||||
if (options & CMARK_OPT_SAFE) {
|
||||
cmark_strbuf_puts(html, "<!-- raw HTML omitted -->");
|
||||
} else {
|
||||
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
|
||||
}
|
||||
cr(html);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
cr(html);
|
||||
if (entering) {
|
||||
cmark_strbuf_put(html, node->as.custom.on_enter.data,
|
||||
node->as.custom.on_enter.len);
|
||||
} else {
|
||||
cmark_strbuf_put(html, node->as.custom.on_exit.data,
|
||||
node->as.custom.on_exit.len);
|
||||
}
|
||||
cr(html);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_THEMATIC_BREAK:
|
||||
cr(html);
|
||||
cmark_strbuf_puts(html, "<hr");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_puts(html, " />\n");
|
||||
break;
|
||||
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
parent = cmark_node_parent(node);
|
||||
grandparent = cmark_node_parent(parent);
|
||||
if (grandparent != NULL && grandparent->type == CMARK_NODE_LIST) {
|
||||
tight = grandparent->as.list.tight;
|
||||
} else {
|
||||
tight = false;
|
||||
}
|
||||
if (!tight) {
|
||||
if (entering) {
|
||||
cr(html);
|
||||
cmark_strbuf_puts(html, "<p");
|
||||
S_render_sourcepos(node, html, options);
|
||||
cmark_strbuf_putc(html, '>');
|
||||
} else {
|
||||
cmark_strbuf_puts(html, "</p>\n");
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_TEXT:
|
||||
escape_html(html, node->as.literal.data, node->as.literal.len);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINEBREAK:
|
||||
cmark_strbuf_puts(html, "<br />\n");
|
||||
break;
|
||||
|
||||
case CMARK_NODE_SOFTBREAK:
|
||||
if (options & CMARK_OPT_HARDBREAKS) {
|
||||
cmark_strbuf_puts(html, "<br />\n");
|
||||
} else if (options & CMARK_OPT_NOBREAKS) {
|
||||
cmark_strbuf_putc(html, ' ');
|
||||
} else {
|
||||
cmark_strbuf_putc(html, '\n');
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE:
|
||||
cmark_strbuf_puts(html, "<code>");
|
||||
escape_html(html, node->as.literal.data, node->as.literal.len);
|
||||
cmark_strbuf_puts(html, "</code>");
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
if (options & CMARK_OPT_SAFE) {
|
||||
cmark_strbuf_puts(html, "<!-- raw HTML omitted -->");
|
||||
} else {
|
||||
cmark_strbuf_put(html, node->as.literal.data, node->as.literal.len);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
if (entering) {
|
||||
cmark_strbuf_put(html, node->as.custom.on_enter.data,
|
||||
node->as.custom.on_enter.len);
|
||||
} else {
|
||||
cmark_strbuf_put(html, node->as.custom.on_exit.data,
|
||||
node->as.custom.on_exit.len);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_STRONG:
|
||||
if (entering) {
|
||||
cmark_strbuf_puts(html, "<strong>");
|
||||
} else {
|
||||
cmark_strbuf_puts(html, "</strong>");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_EMPH:
|
||||
if (entering) {
|
||||
cmark_strbuf_puts(html, "<em>");
|
||||
} else {
|
||||
cmark_strbuf_puts(html, "</em>");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINK:
|
||||
if (entering) {
|
||||
cmark_strbuf_puts(html, "<a href=\"");
|
||||
if (!((options & CMARK_OPT_SAFE) &&
|
||||
scan_dangerous_url(&node->as.link.url, 0))) {
|
||||
houdini_escape_href(html, node->as.link.url.data,
|
||||
node->as.link.url.len);
|
||||
}
|
||||
if (node->as.link.title.len) {
|
||||
cmark_strbuf_puts(html, "\" title=\"");
|
||||
escape_html(html, node->as.link.title.data, node->as.link.title.len);
|
||||
}
|
||||
cmark_strbuf_puts(html, "\">");
|
||||
} else {
|
||||
cmark_strbuf_puts(html, "</a>");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_IMAGE:
|
||||
if (entering) {
|
||||
cmark_strbuf_puts(html, "<img src=\"");
|
||||
if (!((options & CMARK_OPT_SAFE) &&
|
||||
scan_dangerous_url(&node->as.link.url, 0))) {
|
||||
houdini_escape_href(html, node->as.link.url.data,
|
||||
node->as.link.url.len);
|
||||
}
|
||||
cmark_strbuf_puts(html, "\" alt=\"");
|
||||
state->plain = node;
|
||||
} else {
|
||||
if (node->as.link.title.len) {
|
||||
cmark_strbuf_puts(html, "\" title=\"");
|
||||
escape_html(html, node->as.link.title.data, node->as.link.title.len);
|
||||
}
|
||||
|
||||
cmark_strbuf_puts(html, "\" />");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
// cmark_strbuf_putc(html, 'x');
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *cmark_render_html(cmark_node *root, int options) {
|
||||
char *result;
|
||||
cmark_strbuf html = CMARK_BUF_INIT(cmark_node_mem(root));
|
||||
cmark_event_type ev_type;
|
||||
cmark_node *cur;
|
||||
struct render_state state = {&html, NULL};
|
||||
cmark_iter *iter = cmark_iter_new(root);
|
||||
|
||||
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
||||
cur = cmark_iter_get_node(iter);
|
||||
S_render_node(cur, ev_type, &state, options);
|
||||
}
|
||||
result = (char *)cmark_strbuf_detach(&html);
|
||||
|
||||
cmark_iter_free(iter);
|
||||
return result;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,21 @@
|
|||
#ifndef CMARK_INLINES_H
|
||||
#define CMARK_INLINES_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
cmark_chunk cmark_clean_url(cmark_mem *mem, cmark_chunk *url);
|
||||
cmark_chunk cmark_clean_title(cmark_mem *mem, cmark_chunk *title);
|
||||
|
||||
void cmark_parse_inlines(cmark_mem *mem, cmark_node *parent,
|
||||
cmark_reference_map *refmap, int options);
|
||||
|
||||
bufsize_t cmark_parse_reference_inline(cmark_mem *mem, cmark_chunk *input,
|
||||
cmark_reference_map *refmap);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,121 @@
|
|||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "node.h"
|
||||
#include "cmark.h"
|
||||
#include "iterator.h"
|
||||
|
||||
static const int S_leaf_mask =
|
||||
(1 << CMARK_NODE_HTML_BLOCK) | (1 << CMARK_NODE_THEMATIC_BREAK) |
|
||||
(1 << CMARK_NODE_CODE_BLOCK) | (1 << CMARK_NODE_TEXT) |
|
||||
(1 << CMARK_NODE_SOFTBREAK) | (1 << CMARK_NODE_LINEBREAK) |
|
||||
(1 << CMARK_NODE_CODE) | (1 << CMARK_NODE_HTML_INLINE);
|
||||
|
||||
cmark_iter *cmark_iter_new(cmark_node *root) {
|
||||
if (root == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
cmark_mem *mem = root->content.mem;
|
||||
cmark_iter *iter = (cmark_iter *)mem->calloc(1, sizeof(cmark_iter));
|
||||
iter->mem = mem;
|
||||
iter->root = root;
|
||||
iter->cur.ev_type = CMARK_EVENT_NONE;
|
||||
iter->cur.node = NULL;
|
||||
iter->next.ev_type = CMARK_EVENT_ENTER;
|
||||
iter->next.node = root;
|
||||
return iter;
|
||||
}
|
||||
|
||||
void cmark_iter_free(cmark_iter *iter) { iter->mem->free(iter); }
|
||||
|
||||
static bool S_is_leaf(cmark_node *node) {
|
||||
return ((1 << node->type) & S_leaf_mask) != 0;
|
||||
}
|
||||
|
||||
cmark_event_type cmark_iter_next(cmark_iter *iter) {
|
||||
cmark_event_type ev_type = iter->next.ev_type;
|
||||
cmark_node *node = iter->next.node;
|
||||
|
||||
iter->cur.ev_type = ev_type;
|
||||
iter->cur.node = node;
|
||||
|
||||
if (ev_type == CMARK_EVENT_DONE) {
|
||||
return ev_type;
|
||||
}
|
||||
|
||||
/* roll forward to next item, setting both fields */
|
||||
if (ev_type == CMARK_EVENT_ENTER && !S_is_leaf(node)) {
|
||||
if (node->first_child == NULL) {
|
||||
/* stay on this node but exit */
|
||||
iter->next.ev_type = CMARK_EVENT_EXIT;
|
||||
} else {
|
||||
iter->next.ev_type = CMARK_EVENT_ENTER;
|
||||
iter->next.node = node->first_child;
|
||||
}
|
||||
} else if (node == iter->root) {
|
||||
/* don't move past root */
|
||||
iter->next.ev_type = CMARK_EVENT_DONE;
|
||||
iter->next.node = NULL;
|
||||
} else if (node->next) {
|
||||
iter->next.ev_type = CMARK_EVENT_ENTER;
|
||||
iter->next.node = node->next;
|
||||
} else if (node->parent) {
|
||||
iter->next.ev_type = CMARK_EVENT_EXIT;
|
||||
iter->next.node = node->parent;
|
||||
} else {
|
||||
assert(false);
|
||||
iter->next.ev_type = CMARK_EVENT_DONE;
|
||||
iter->next.node = NULL;
|
||||
}
|
||||
|
||||
return ev_type;
|
||||
}
|
||||
|
||||
void cmark_iter_reset(cmark_iter *iter, cmark_node *current,
|
||||
cmark_event_type event_type) {
|
||||
iter->next.ev_type = event_type;
|
||||
iter->next.node = current;
|
||||
cmark_iter_next(iter);
|
||||
}
|
||||
|
||||
cmark_node *cmark_iter_get_node(cmark_iter *iter) { return iter->cur.node; }
|
||||
|
||||
cmark_event_type cmark_iter_get_event_type(cmark_iter *iter) {
|
||||
return iter->cur.ev_type;
|
||||
}
|
||||
|
||||
cmark_node *cmark_iter_get_root(cmark_iter *iter) { return iter->root; }
|
||||
|
||||
void cmark_consolidate_text_nodes(cmark_node *root) {
|
||||
if (root == NULL) {
|
||||
return;
|
||||
}
|
||||
cmark_iter *iter = cmark_iter_new(root);
|
||||
cmark_strbuf buf = CMARK_BUF_INIT(iter->mem);
|
||||
cmark_event_type ev_type;
|
||||
cmark_node *cur, *tmp, *next;
|
||||
|
||||
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
||||
cur = cmark_iter_get_node(iter);
|
||||
if (ev_type == CMARK_EVENT_ENTER && cur->type == CMARK_NODE_TEXT &&
|
||||
cur->next && cur->next->type == CMARK_NODE_TEXT) {
|
||||
cmark_strbuf_clear(&buf);
|
||||
cmark_strbuf_put(&buf, cur->as.literal.data, cur->as.literal.len);
|
||||
tmp = cur->next;
|
||||
while (tmp && tmp->type == CMARK_NODE_TEXT) {
|
||||
cmark_iter_next(iter); // advance pointer
|
||||
cmark_strbuf_put(&buf, tmp->as.literal.data, tmp->as.literal.len);
|
||||
cur->end_column = tmp->end_column;
|
||||
next = tmp->next;
|
||||
cmark_node_free(tmp);
|
||||
tmp = next;
|
||||
}
|
||||
cmark_chunk_free(iter->mem, &cur->as.literal);
|
||||
cur->as.literal = cmark_chunk_buf_detach(&buf);
|
||||
}
|
||||
}
|
||||
|
||||
cmark_strbuf_free(&buf);
|
||||
cmark_iter_free(iter);
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
#ifndef CMARK_ITERATOR_H
|
||||
#define CMARK_ITERATOR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "cmark.h"
|
||||
#include "memory.h"
|
||||
|
||||
typedef struct {
|
||||
cmark_event_type ev_type;
|
||||
cmark_node *node;
|
||||
} cmark_iter_state;
|
||||
|
||||
struct cmark_iter {
|
||||
cmark_mem *mem;
|
||||
cmark_node *root;
|
||||
cmark_iter_state cur;
|
||||
cmark_iter_state next;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,453 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "cmark.h"
|
||||
#include "node.h"
|
||||
#include "buffer.h"
|
||||
#include "utf8.h"
|
||||
#include "scanners.h"
|
||||
#include "render.h"
|
||||
|
||||
#define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping)
|
||||
#define LIT(s) renderer->out(renderer, s, false, LITERAL)
|
||||
#define CR() renderer->cr(renderer)
|
||||
#define BLANKLINE() renderer->blankline(renderer)
|
||||
#define LIST_NUMBER_STRING_SIZE 20
|
||||
|
||||
static CMARK_INLINE void outc(cmark_renderer *renderer, cmark_escaping escape,
|
||||
int32_t c, unsigned char nextc) {
|
||||
if (escape == LITERAL) {
|
||||
cmark_render_code_point(renderer, c);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 123: // '{'
|
||||
case 125: // '}'
|
||||
case 35: // '#'
|
||||
case 37: // '%'
|
||||
case 38: // '&'
|
||||
cmark_render_ascii(renderer, "\\");
|
||||
cmark_render_code_point(renderer, c);
|
||||
break;
|
||||
case 36: // '$'
|
||||
case 95: // '_'
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "\\");
|
||||
}
|
||||
cmark_render_code_point(renderer, c);
|
||||
break;
|
||||
case 45: // '-'
|
||||
if (nextc == 45) { // prevent ligature
|
||||
cmark_render_ascii(renderer, "-{}");
|
||||
} else {
|
||||
cmark_render_ascii(renderer, "-");
|
||||
}
|
||||
break;
|
||||
case 126: // '~'
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "\\textasciitilde{}");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 94: // '^'
|
||||
cmark_render_ascii(renderer, "\\^{}");
|
||||
break;
|
||||
case 92: // '\\'
|
||||
if (escape == URL) {
|
||||
// / acts as path sep even on windows:
|
||||
cmark_render_ascii(renderer, "/");
|
||||
} else {
|
||||
cmark_render_ascii(renderer, "\\textbackslash{}");
|
||||
}
|
||||
break;
|
||||
case 124: // '|'
|
||||
cmark_render_ascii(renderer, "\\textbar{}");
|
||||
break;
|
||||
case 60: // '<'
|
||||
cmark_render_ascii(renderer, "\\textless{}");
|
||||
break;
|
||||
case 62: // '>'
|
||||
cmark_render_ascii(renderer, "\\textgreater{}");
|
||||
break;
|
||||
case 91: // '['
|
||||
case 93: // ']'
|
||||
cmark_render_ascii(renderer, "{");
|
||||
cmark_render_code_point(renderer, c);
|
||||
cmark_render_ascii(renderer, "}");
|
||||
break;
|
||||
case 34: // '"'
|
||||
cmark_render_ascii(renderer, "\\textquotedbl{}");
|
||||
// requires \usepackage[T1]{fontenc}
|
||||
break;
|
||||
case 39: // '\''
|
||||
cmark_render_ascii(renderer, "\\textquotesingle{}");
|
||||
// requires \usepackage{textcomp}
|
||||
break;
|
||||
case 160: // nbsp
|
||||
cmark_render_ascii(renderer, "~");
|
||||
break;
|
||||
case 8230: // hellip
|
||||
cmark_render_ascii(renderer, "\\ldots{}");
|
||||
break;
|
||||
case 8216: // lsquo
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "`");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 8217: // rsquo
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "\'");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 8220: // ldquo
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "``");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 8221: // rdquo
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "''");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 8212: // emdash
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "---");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 8211: // endash
|
||||
if (escape == NORMAL) {
|
||||
cmark_render_ascii(renderer, "--");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
}
|
||||
|
||||
typedef enum {
|
||||
NO_LINK,
|
||||
URL_AUTOLINK,
|
||||
EMAIL_AUTOLINK,
|
||||
NORMAL_LINK,
|
||||
INTERNAL_LINK
|
||||
} link_type;
|
||||
|
||||
static link_type get_link_type(cmark_node *node) {
|
||||
size_t title_len, url_len;
|
||||
cmark_node *link_text;
|
||||
char *realurl;
|
||||
int realurllen;
|
||||
bool isemail = false;
|
||||
|
||||
if (node->type != CMARK_NODE_LINK) {
|
||||
return NO_LINK;
|
||||
}
|
||||
|
||||
const char *url = cmark_node_get_url(node);
|
||||
cmark_chunk url_chunk = cmark_chunk_literal(url);
|
||||
|
||||
if (url && *url == '#') {
|
||||
return INTERNAL_LINK;
|
||||
}
|
||||
|
||||
url_len = strlen(url);
|
||||
if (url_len == 0 || scan_scheme(&url_chunk, 0) == 0) {
|
||||
return NO_LINK;
|
||||
}
|
||||
|
||||
const char *title = cmark_node_get_title(node);
|
||||
title_len = strlen(title);
|
||||
// if it has a title, we can't treat it as an autolink:
|
||||
if (title_len == 0) {
|
||||
|
||||
link_text = node->first_child;
|
||||
cmark_consolidate_text_nodes(link_text);
|
||||
|
||||
if (!link_text)
|
||||
return NO_LINK;
|
||||
|
||||
realurl = (char *)url;
|
||||
realurllen = (int)url_len;
|
||||
if (strncmp(realurl, "mailto:", 7) == 0) {
|
||||
realurl += 7;
|
||||
realurllen -= 7;
|
||||
isemail = true;
|
||||
}
|
||||
if (realurllen == link_text->as.literal.len &&
|
||||
strncmp(realurl, (char *)link_text->as.literal.data,
|
||||
link_text->as.literal.len) == 0) {
|
||||
if (isemail) {
|
||||
return EMAIL_AUTOLINK;
|
||||
} else {
|
||||
return URL_AUTOLINK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return NORMAL_LINK;
|
||||
}
|
||||
|
||||
static int S_get_enumlevel(cmark_node *node) {
|
||||
int enumlevel = 0;
|
||||
cmark_node *tmp = node;
|
||||
while (tmp) {
|
||||
if (tmp->type == CMARK_NODE_LIST &&
|
||||
cmark_node_get_list_type(node) == CMARK_ORDERED_LIST) {
|
||||
enumlevel++;
|
||||
}
|
||||
tmp = tmp->parent;
|
||||
}
|
||||
return enumlevel;
|
||||
}
|
||||
|
||||
static int S_render_node(cmark_renderer *renderer, cmark_node *node,
|
||||
cmark_event_type ev_type, int options) {
|
||||
int list_number;
|
||||
int enumlevel;
|
||||
char list_number_string[LIST_NUMBER_STRING_SIZE];
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
cmark_list_type list_type;
|
||||
bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options);
|
||||
|
||||
// avoid warning about unused parameter:
|
||||
(void)(options);
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_DOCUMENT:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_BLOCK_QUOTE:
|
||||
if (entering) {
|
||||
LIT("\\begin{quote}");
|
||||
CR();
|
||||
} else {
|
||||
LIT("\\end{quote}");
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LIST:
|
||||
list_type = cmark_node_get_list_type(node);
|
||||
if (entering) {
|
||||
LIT("\\begin{");
|
||||
LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize");
|
||||
LIT("}");
|
||||
CR();
|
||||
list_number = cmark_node_get_list_start(node);
|
||||
if (list_number > 1) {
|
||||
enumlevel = S_get_enumlevel(node);
|
||||
// latex normally supports only five levels
|
||||
if (enumlevel >= 1 && enumlevel <= 5) {
|
||||
snprintf(list_number_string, LIST_NUMBER_STRING_SIZE, "%d",
|
||||
list_number);
|
||||
LIT("\\setcounter{enum");
|
||||
switch (enumlevel) {
|
||||
case 1: LIT("i"); break;
|
||||
case 2: LIT("ii"); break;
|
||||
case 3: LIT("iii"); break;
|
||||
case 4: LIT("iv"); break;
|
||||
case 5: LIT("v"); break;
|
||||
default: LIT("i"); break;
|
||||
}
|
||||
LIT("}{");
|
||||
OUT(list_number_string, false, NORMAL);
|
||||
LIT("}");
|
||||
}
|
||||
CR();
|
||||
}
|
||||
} else {
|
||||
LIT("\\end{");
|
||||
LIT(list_type == CMARK_ORDERED_LIST ? "enumerate" : "itemize");
|
||||
LIT("}");
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_ITEM:
|
||||
if (entering) {
|
||||
LIT("\\item ");
|
||||
} else {
|
||||
CR();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HEADING:
|
||||
if (entering) {
|
||||
switch (cmark_node_get_heading_level(node)) {
|
||||
case 1:
|
||||
LIT("\\section");
|
||||
break;
|
||||
case 2:
|
||||
LIT("\\subsection");
|
||||
break;
|
||||
case 3:
|
||||
LIT("\\subsubsection");
|
||||
break;
|
||||
case 4:
|
||||
LIT("\\paragraph");
|
||||
break;
|
||||
case 5:
|
||||
LIT("\\subparagraph");
|
||||
break;
|
||||
}
|
||||
LIT("{");
|
||||
} else {
|
||||
LIT("}");
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
CR();
|
||||
LIT("\\begin{verbatim}");
|
||||
CR();
|
||||
OUT(cmark_node_get_literal(node), false, LITERAL);
|
||||
CR();
|
||||
LIT("\\end{verbatim}");
|
||||
BLANKLINE();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
CR();
|
||||
OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
|
||||
false, LITERAL);
|
||||
CR();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_THEMATIC_BREAK:
|
||||
BLANKLINE();
|
||||
LIT("\\begin{center}\\rule{0.5\\linewidth}{\\linethickness}\\end{center}");
|
||||
BLANKLINE();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
if (!entering) {
|
||||
BLANKLINE();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_TEXT:
|
||||
OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINEBREAK:
|
||||
LIT("\\\\");
|
||||
CR();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_SOFTBREAK:
|
||||
if (options & CMARK_OPT_HARDBREAKS) {
|
||||
LIT("\\\\");
|
||||
CR();
|
||||
} else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) {
|
||||
CR();
|
||||
} else {
|
||||
OUT(" ", allow_wrap, NORMAL);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE:
|
||||
LIT("\\texttt{");
|
||||
OUT(cmark_node_get_literal(node), false, NORMAL);
|
||||
LIT("}");
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
|
||||
false, LITERAL);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_STRONG:
|
||||
if (entering) {
|
||||
LIT("\\textbf{");
|
||||
} else {
|
||||
LIT("}");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_EMPH:
|
||||
if (entering) {
|
||||
LIT("\\emph{");
|
||||
} else {
|
||||
LIT("}");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINK:
|
||||
if (entering) {
|
||||
const char *url = cmark_node_get_url(node);
|
||||
// requires \usepackage{hyperref}
|
||||
switch (get_link_type(node)) {
|
||||
case URL_AUTOLINK:
|
||||
LIT("\\url{");
|
||||
OUT(url, false, URL);
|
||||
LIT("}");
|
||||
return 0; // Don't process further nodes to avoid double-rendering artefacts
|
||||
case EMAIL_AUTOLINK:
|
||||
LIT("\\href{");
|
||||
OUT(url, false, URL);
|
||||
LIT("}\\nolinkurl{");
|
||||
break;
|
||||
case NORMAL_LINK:
|
||||
LIT("\\href{");
|
||||
OUT(url, false, URL);
|
||||
LIT("}{");
|
||||
break;
|
||||
case INTERNAL_LINK:
|
||||
LIT("\\protect\\hyperlink{");
|
||||
OUT(url + 1, false, URL);
|
||||
LIT("}{");
|
||||
break;
|
||||
case NO_LINK:
|
||||
LIT("{"); // error?
|
||||
}
|
||||
} else {
|
||||
LIT("}");
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case CMARK_NODE_IMAGE:
|
||||
if (entering) {
|
||||
LIT("\\protect\\includegraphics{");
|
||||
// requires \include{graphicx}
|
||||
OUT(cmark_node_get_url(node), false, URL);
|
||||
LIT("}");
|
||||
return 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *cmark_render_latex(cmark_node *root, int options, int width) {
|
||||
return cmark_render(root, options, width, outc, S_render_node);
|
||||
}
|
|
@ -0,0 +1,211 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include "config.h"
|
||||
#include "memory.h"
|
||||
#include "cmark.h"
|
||||
#include "node.h"
|
||||
|
||||
#if defined(__OpenBSD__)
|
||||
# include <sys/param.h>
|
||||
# if OpenBSD >= 201605
|
||||
# define USE_PLEDGE
|
||||
# include <unistd.h>
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(__CYGWIN__)
|
||||
#include <io.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
FORMAT_NONE,
|
||||
FORMAT_HTML,
|
||||
FORMAT_XML,
|
||||
FORMAT_MAN,
|
||||
FORMAT_COMMONMARK,
|
||||
FORMAT_LATEX
|
||||
} writer_format;
|
||||
|
||||
void print_usage() {
|
||||
printf("Usage: cmark [FILE*]\n");
|
||||
printf("Options:\n");
|
||||
printf(" --to, -t FORMAT Specify output format (html, xml, man, "
|
||||
"commonmark, latex)\n");
|
||||
printf(" --width WIDTH Specify wrap width (default 0 = nowrap)\n");
|
||||
printf(" --sourcepos Include source position attribute\n");
|
||||
printf(" --hardbreaks Treat newlines as hard line breaks\n");
|
||||
printf(" --nobreaks Render soft line breaks as spaces\n");
|
||||
printf(" --safe Suppress raw HTML and dangerous URLs\n");
|
||||
printf(" --smart Use smart punctuation\n");
|
||||
printf(" --validate-utf8 Replace UTF-8 invalid sequences with U+FFFD\n");
|
||||
printf(" --help, -h Print usage information\n");
|
||||
printf(" --version Print version\n");
|
||||
}
|
||||
|
||||
static void print_document(cmark_node *document, writer_format writer,
|
||||
int options, int width) {
|
||||
char *result;
|
||||
|
||||
switch (writer) {
|
||||
case FORMAT_HTML:
|
||||
result = cmark_render_html(document, options);
|
||||
break;
|
||||
case FORMAT_XML:
|
||||
result = cmark_render_xml(document, options);
|
||||
break;
|
||||
case FORMAT_MAN:
|
||||
result = cmark_render_man(document, options, width);
|
||||
break;
|
||||
case FORMAT_COMMONMARK:
|
||||
result = cmark_render_commonmark(document, options, width);
|
||||
break;
|
||||
case FORMAT_LATEX:
|
||||
result = cmark_render_latex(document, options, width);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Unknown format %d\n", writer);
|
||||
exit(1);
|
||||
}
|
||||
printf("%s", result);
|
||||
cmark_node_mem(document)->free(result);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
int i, numfps = 0;
|
||||
int *files;
|
||||
char buffer[4096];
|
||||
cmark_parser *parser;
|
||||
size_t bytes;
|
||||
cmark_node *document;
|
||||
int width = 0;
|
||||
char *unparsed;
|
||||
writer_format writer = FORMAT_HTML;
|
||||
int options = CMARK_OPT_DEFAULT;
|
||||
|
||||
#ifdef USE_PLEDGE
|
||||
if (pledge("stdio rpath", NULL) != 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && !defined(__CYGWIN__)
|
||||
_setmode(_fileno(stdin), _O_BINARY);
|
||||
_setmode(_fileno(stdout), _O_BINARY);
|
||||
#endif
|
||||
|
||||
files = (int *)calloc(argc, sizeof(*files));
|
||||
|
||||
for (i = 1; i < argc; i++) {
|
||||
if (strcmp(argv[i], "--version") == 0) {
|
||||
printf("cmark %s", CMARK_VERSION_STRING);
|
||||
printf(" - CommonMark converter\n(C) 2014-2016 John MacFarlane\n");
|
||||
exit(0);
|
||||
} else if (strcmp(argv[i], "--sourcepos") == 0) {
|
||||
options |= CMARK_OPT_SOURCEPOS;
|
||||
} else if (strcmp(argv[i], "--hardbreaks") == 0) {
|
||||
options |= CMARK_OPT_HARDBREAKS;
|
||||
} else if (strcmp(argv[i], "--nobreaks") == 0) {
|
||||
options |= CMARK_OPT_NOBREAKS;
|
||||
} else if (strcmp(argv[i], "--smart") == 0) {
|
||||
options |= CMARK_OPT_SMART;
|
||||
} else if (strcmp(argv[i], "--safe") == 0) {
|
||||
options |= CMARK_OPT_SAFE;
|
||||
} else if (strcmp(argv[i], "--validate-utf8") == 0) {
|
||||
options |= CMARK_OPT_VALIDATE_UTF8;
|
||||
} else if ((strcmp(argv[i], "--help") == 0) ||
|
||||
(strcmp(argv[i], "-h") == 0)) {
|
||||
print_usage();
|
||||
exit(0);
|
||||
} else if (strcmp(argv[i], "--width") == 0) {
|
||||
i += 1;
|
||||
if (i < argc) {
|
||||
width = (int)strtol(argv[i], &unparsed, 10);
|
||||
if (unparsed && strlen(unparsed) > 0) {
|
||||
fprintf(stderr, "failed parsing width '%s' at '%s'\n", argv[i],
|
||||
unparsed);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "--width requires an argument\n");
|
||||
exit(1);
|
||||
}
|
||||
} else if ((strcmp(argv[i], "-t") == 0) || (strcmp(argv[i], "--to") == 0)) {
|
||||
i += 1;
|
||||
if (i < argc) {
|
||||
if (strcmp(argv[i], "man") == 0) {
|
||||
writer = FORMAT_MAN;
|
||||
} else if (strcmp(argv[i], "html") == 0) {
|
||||
writer = FORMAT_HTML;
|
||||
} else if (strcmp(argv[i], "xml") == 0) {
|
||||
writer = FORMAT_XML;
|
||||
} else if (strcmp(argv[i], "commonmark") == 0) {
|
||||
writer = FORMAT_COMMONMARK;
|
||||
} else if (strcmp(argv[i], "latex") == 0) {
|
||||
writer = FORMAT_LATEX;
|
||||
} else {
|
||||
fprintf(stderr, "Unknown format %s\n", argv[i]);
|
||||
exit(1);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "No argument provided for %s\n", argv[i - 1]);
|
||||
exit(1);
|
||||
}
|
||||
} else if (*argv[i] == '-') {
|
||||
print_usage();
|
||||
exit(1);
|
||||
} else { // treat as file argument
|
||||
files[numfps++] = i;
|
||||
}
|
||||
}
|
||||
|
||||
parser = cmark_parser_new(options);
|
||||
for (i = 0; i < numfps; i++) {
|
||||
FILE *fp = fopen(argv[files[i]], "rb");
|
||||
if (fp == NULL) {
|
||||
fprintf(stderr, "Error opening file %s: %s\n", argv[files[i]],
|
||||
strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
while ((bytes = fread(buffer, 1, sizeof(buffer), fp)) > 0) {
|
||||
cmark_parser_feed(parser, buffer, bytes);
|
||||
if (bytes < sizeof(buffer)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(fp);
|
||||
}
|
||||
|
||||
if (numfps == 0) {
|
||||
|
||||
while ((bytes = fread(buffer, 1, sizeof(buffer), stdin)) > 0) {
|
||||
cmark_parser_feed(parser, buffer, bytes);
|
||||
if (bytes < sizeof(buffer)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_PLEDGE
|
||||
if (pledge("stdio", NULL) != 0) {
|
||||
perror("pledge");
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
document = cmark_parser_finish(parser);
|
||||
cmark_parser_free(parser);
|
||||
|
||||
print_document(document, writer, options, width);
|
||||
|
||||
cmark_node_free(document);
|
||||
|
||||
free(files);
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,252 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "cmark.h"
|
||||
#include "node.h"
|
||||
#include "buffer.h"
|
||||
#include "utf8.h"
|
||||
#include "render.h"
|
||||
|
||||
#define OUT(s, wrap, escaping) renderer->out(renderer, s, wrap, escaping)
|
||||
#define LIT(s) renderer->out(renderer, s, false, LITERAL)
|
||||
#define CR() renderer->cr(renderer)
|
||||
#define BLANKLINE() renderer->blankline(renderer)
|
||||
#define LIST_NUMBER_SIZE 20
|
||||
|
||||
// Functions to convert cmark_nodes to groff man strings.
|
||||
static void S_outc(cmark_renderer *renderer, cmark_escaping escape, int32_t c,
|
||||
unsigned char nextc) {
|
||||
(void)(nextc);
|
||||
|
||||
if (escape == LITERAL) {
|
||||
cmark_render_code_point(renderer, c);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (c) {
|
||||
case 46:
|
||||
if (renderer->begin_line) {
|
||||
cmark_render_ascii(renderer, "\\&.");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 39:
|
||||
if (renderer->begin_line) {
|
||||
cmark_render_ascii(renderer, "\\&'");
|
||||
} else {
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
break;
|
||||
case 45:
|
||||
cmark_render_ascii(renderer, "\\-");
|
||||
break;
|
||||
case 92:
|
||||
cmark_render_ascii(renderer, "\\e");
|
||||
break;
|
||||
case 8216: // left single quote
|
||||
cmark_render_ascii(renderer, "\\[oq]");
|
||||
break;
|
||||
case 8217: // right single quote
|
||||
cmark_render_ascii(renderer, "\\[cq]");
|
||||
break;
|
||||
case 8220: // left double quote
|
||||
cmark_render_ascii(renderer, "\\[lq]");
|
||||
break;
|
||||
case 8221: // right double quote
|
||||
cmark_render_ascii(renderer, "\\[rq]");
|
||||
break;
|
||||
case 8212: // em dash
|
||||
cmark_render_ascii(renderer, "\\[em]");
|
||||
break;
|
||||
case 8211: // en dash
|
||||
cmark_render_ascii(renderer, "\\[en]");
|
||||
break;
|
||||
default:
|
||||
cmark_render_code_point(renderer, c);
|
||||
}
|
||||
}
|
||||
|
||||
static int S_render_node(cmark_renderer *renderer, cmark_node *node,
|
||||
cmark_event_type ev_type, int options) {
|
||||
cmark_node *tmp;
|
||||
int list_number;
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
bool allow_wrap = renderer->width > 0 && !(CMARK_OPT_NOBREAKS & options);
|
||||
|
||||
// avoid unused parameter error:
|
||||
(void)(options);
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_DOCUMENT:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_BLOCK_QUOTE:
|
||||
if (entering) {
|
||||
CR();
|
||||
LIT(".RS");
|
||||
CR();
|
||||
} else {
|
||||
CR();
|
||||
LIT(".RE");
|
||||
CR();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LIST:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_ITEM:
|
||||
if (entering) {
|
||||
CR();
|
||||
LIT(".IP ");
|
||||
if (cmark_node_get_list_type(node->parent) == CMARK_BULLET_LIST) {
|
||||
LIT("\\[bu] 2");
|
||||
} else {
|
||||
list_number = cmark_node_get_list_start(node->parent);
|
||||
tmp = node;
|
||||
while (tmp->prev) {
|
||||
tmp = tmp->prev;
|
||||
list_number += 1;
|
||||
}
|
||||
char list_number_s[LIST_NUMBER_SIZE];
|
||||
snprintf(list_number_s, LIST_NUMBER_SIZE, "\"%d.\" 4", list_number);
|
||||
LIT(list_number_s);
|
||||
}
|
||||
CR();
|
||||
} else {
|
||||
CR();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HEADING:
|
||||
if (entering) {
|
||||
CR();
|
||||
LIT(cmark_node_get_heading_level(node) == 1 ? ".SH" : ".SS");
|
||||
CR();
|
||||
} else {
|
||||
CR();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
CR();
|
||||
LIT(".IP\n.nf\n\\f[C]\n");
|
||||
OUT(cmark_node_get_literal(node), false, NORMAL);
|
||||
CR();
|
||||
LIT("\\f[]\n.fi");
|
||||
CR();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
CR();
|
||||
OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
|
||||
false, LITERAL);
|
||||
CR();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_THEMATIC_BREAK:
|
||||
CR();
|
||||
LIT(".PP\n * * * * *");
|
||||
CR();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
if (entering) {
|
||||
// no blank line if first paragraph in list:
|
||||
if (node->parent && node->parent->type == CMARK_NODE_ITEM &&
|
||||
node->prev == NULL) {
|
||||
// no blank line or .PP
|
||||
} else {
|
||||
CR();
|
||||
LIT(".PP");
|
||||
CR();
|
||||
}
|
||||
} else {
|
||||
CR();
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_TEXT:
|
||||
OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINEBREAK:
|
||||
LIT(".PD 0\n.P\n.PD");
|
||||
CR();
|
||||
break;
|
||||
|
||||
case CMARK_NODE_SOFTBREAK:
|
||||
if (options & CMARK_OPT_HARDBREAKS) {
|
||||
LIT(".PD 0\n.P\n.PD");
|
||||
CR();
|
||||
} else if (renderer->width == 0 && !(CMARK_OPT_NOBREAKS & options)) {
|
||||
CR();
|
||||
} else {
|
||||
OUT(" ", allow_wrap, LITERAL);
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CODE:
|
||||
LIT("\\f[C]");
|
||||
OUT(cmark_node_get_literal(node), allow_wrap, NORMAL);
|
||||
LIT("\\f[]");
|
||||
break;
|
||||
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
break;
|
||||
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
OUT(entering ? cmark_node_get_on_enter(node) : cmark_node_get_on_exit(node),
|
||||
false, LITERAL);
|
||||
break;
|
||||
|
||||
case CMARK_NODE_STRONG:
|
||||
if (entering) {
|
||||
LIT("\\f[B]");
|
||||
} else {
|
||||
LIT("\\f[]");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_EMPH:
|
||||
if (entering) {
|
||||
LIT("\\f[I]");
|
||||
} else {
|
||||
LIT("\\f[]");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LINK:
|
||||
if (!entering) {
|
||||
LIT(" (");
|
||||
OUT(cmark_node_get_url(node), allow_wrap, URL);
|
||||
LIT(")");
|
||||
}
|
||||
break;
|
||||
|
||||
case CMARK_NODE_IMAGE:
|
||||
if (entering) {
|
||||
LIT("[IMAGE: ");
|
||||
} else {
|
||||
LIT("]");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *cmark_render_man(cmark_node *root, int options, int width) {
|
||||
return cmark_render(root, options, width, S_outc, S_render_node);
|
||||
}
|
|
@ -0,0 +1,858 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "node.h"
|
||||
|
||||
static void S_node_unlink(cmark_node *node);
|
||||
|
||||
#define NODE_MEM(node) cmark_node_mem(node)
|
||||
|
||||
static CMARK_INLINE bool S_is_block(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return false;
|
||||
}
|
||||
return node->type >= CMARK_NODE_FIRST_BLOCK &&
|
||||
node->type <= CMARK_NODE_LAST_BLOCK;
|
||||
}
|
||||
|
||||
static CMARK_INLINE bool S_is_inline(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return false;
|
||||
}
|
||||
return node->type >= CMARK_NODE_FIRST_INLINE &&
|
||||
node->type <= CMARK_NODE_LAST_INLINE;
|
||||
}
|
||||
|
||||
static bool S_can_contain(cmark_node *node, cmark_node *child) {
|
||||
cmark_node *cur;
|
||||
|
||||
if (node == NULL || child == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that child is not an ancestor of node or equal to node.
|
||||
cur = node;
|
||||
do {
|
||||
if (cur == child) {
|
||||
return false;
|
||||
}
|
||||
cur = cur->parent;
|
||||
} while (cur != NULL);
|
||||
|
||||
if (child->type == CMARK_NODE_DOCUMENT) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_DOCUMENT:
|
||||
case CMARK_NODE_BLOCK_QUOTE:
|
||||
case CMARK_NODE_ITEM:
|
||||
return S_is_block(child) && child->type != CMARK_NODE_ITEM;
|
||||
|
||||
case CMARK_NODE_LIST:
|
||||
return child->type == CMARK_NODE_ITEM;
|
||||
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
return true;
|
||||
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
case CMARK_NODE_HEADING:
|
||||
case CMARK_NODE_EMPH:
|
||||
case CMARK_NODE_STRONG:
|
||||
case CMARK_NODE_LINK:
|
||||
case CMARK_NODE_IMAGE:
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
return S_is_inline(child);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
cmark_node *cmark_node_new_with_mem(cmark_node_type type, cmark_mem *mem) {
|
||||
cmark_node *node = (cmark_node *)mem->calloc(1, sizeof(*node));
|
||||
cmark_strbuf_init(mem, &node->content, 0);
|
||||
node->type = (uint16_t)type;
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_HEADING:
|
||||
node->as.heading.level = 1;
|
||||
break;
|
||||
|
||||
case CMARK_NODE_LIST: {
|
||||
cmark_list *list = &node->as.list;
|
||||
list->list_type = CMARK_BULLET_LIST;
|
||||
list->start = 0;
|
||||
list->tight = false;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
cmark_node *cmark_node_new(cmark_node_type type) {
|
||||
extern cmark_mem DEFAULT_MEM_ALLOCATOR;
|
||||
return cmark_node_new_with_mem(type, &DEFAULT_MEM_ALLOCATOR);
|
||||
}
|
||||
|
||||
// Free a cmark_node list and any children.
|
||||
static void S_free_nodes(cmark_node *e) {
|
||||
cmark_node *next;
|
||||
while (e != NULL) {
|
||||
cmark_strbuf_free(&e->content);
|
||||
switch (e->type) {
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
cmark_chunk_free(NODE_MEM(e), &e->as.code.info);
|
||||
cmark_chunk_free(NODE_MEM(e), &e->as.code.literal);
|
||||
break;
|
||||
case CMARK_NODE_TEXT:
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
case CMARK_NODE_CODE:
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
cmark_chunk_free(NODE_MEM(e), &e->as.literal);
|
||||
break;
|
||||
case CMARK_NODE_LINK:
|
||||
case CMARK_NODE_IMAGE:
|
||||
cmark_chunk_free(NODE_MEM(e), &e->as.link.url);
|
||||
cmark_chunk_free(NODE_MEM(e), &e->as.link.title);
|
||||
break;
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
cmark_chunk_free(NODE_MEM(e), &e->as.custom.on_enter);
|
||||
cmark_chunk_free(NODE_MEM(e), &e->as.custom.on_exit);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (e->last_child) {
|
||||
// Splice children into list
|
||||
e->last_child->next = e->next;
|
||||
e->next = e->first_child;
|
||||
}
|
||||
next = e->next;
|
||||
NODE_MEM(e)->free(e);
|
||||
e = next;
|
||||
}
|
||||
}
|
||||
|
||||
void cmark_node_free(cmark_node *node) {
|
||||
S_node_unlink(node);
|
||||
node->next = NULL;
|
||||
S_free_nodes(node);
|
||||
}
|
||||
|
||||
cmark_node_type cmark_node_get_type(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return CMARK_NODE_NONE;
|
||||
} else {
|
||||
return (cmark_node_type)node->type;
|
||||
}
|
||||
}
|
||||
|
||||
const char *cmark_node_get_type_string(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return "NONE";
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_NONE:
|
||||
return "none";
|
||||
case CMARK_NODE_DOCUMENT:
|
||||
return "document";
|
||||
case CMARK_NODE_BLOCK_QUOTE:
|
||||
return "block_quote";
|
||||
case CMARK_NODE_LIST:
|
||||
return "list";
|
||||
case CMARK_NODE_ITEM:
|
||||
return "item";
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
return "code_block";
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
return "html_block";
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
return "custom_block";
|
||||
case CMARK_NODE_PARAGRAPH:
|
||||
return "paragraph";
|
||||
case CMARK_NODE_HEADING:
|
||||
return "heading";
|
||||
case CMARK_NODE_THEMATIC_BREAK:
|
||||
return "thematic_break";
|
||||
case CMARK_NODE_TEXT:
|
||||
return "text";
|
||||
case CMARK_NODE_SOFTBREAK:
|
||||
return "softbreak";
|
||||
case CMARK_NODE_LINEBREAK:
|
||||
return "linebreak";
|
||||
case CMARK_NODE_CODE:
|
||||
return "code";
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
return "html_inline";
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
return "custom_inline";
|
||||
case CMARK_NODE_EMPH:
|
||||
return "emph";
|
||||
case CMARK_NODE_STRONG:
|
||||
return "strong";
|
||||
case CMARK_NODE_LINK:
|
||||
return "link";
|
||||
case CMARK_NODE_IMAGE:
|
||||
return "image";
|
||||
}
|
||||
|
||||
return "<unknown>";
|
||||
}
|
||||
|
||||
cmark_node *cmark_node_next(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
return node->next;
|
||||
}
|
||||
}
|
||||
|
||||
cmark_node *cmark_node_previous(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
return node->prev;
|
||||
}
|
||||
}
|
||||
|
||||
cmark_node *cmark_node_parent(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
return node->parent;
|
||||
}
|
||||
}
|
||||
|
||||
cmark_node *cmark_node_first_child(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
return node->first_child;
|
||||
}
|
||||
}
|
||||
|
||||
cmark_node *cmark_node_last_child(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
return node->last_child;
|
||||
}
|
||||
}
|
||||
|
||||
void *cmark_node_get_user_data(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
} else {
|
||||
return node->user_data;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_set_user_data(cmark_node *node, void *user_data) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
node->user_data = user_data;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *cmark_node_get_literal(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
case CMARK_NODE_TEXT:
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
case CMARK_NODE_CODE:
|
||||
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.literal);
|
||||
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.literal);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmark_node_set_literal(cmark_node *node, const char *content) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
case CMARK_NODE_TEXT:
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
case CMARK_NODE_CODE:
|
||||
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.literal, content);
|
||||
return 1;
|
||||
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.literal, content);
|
||||
return 1;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmark_node_get_heading_level(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_HEADING:
|
||||
return node->as.heading.level;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmark_node_set_heading_level(cmark_node *node, int level) {
|
||||
if (node == NULL || level < 1 || level > 6) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_HEADING:
|
||||
node->as.heading.level = level;
|
||||
return 1;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
cmark_list_type cmark_node_get_list_type(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return CMARK_NO_LIST;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
return node->as.list.list_type;
|
||||
} else {
|
||||
return CMARK_NO_LIST;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_set_list_type(cmark_node *node, cmark_list_type type) {
|
||||
if (!(type == CMARK_BULLET_LIST || type == CMARK_ORDERED_LIST)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
node->as.list.list_type = type;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
cmark_delim_type cmark_node_get_list_delim(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return CMARK_NO_DELIM;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
return node->as.list.delimiter;
|
||||
} else {
|
||||
return CMARK_NO_DELIM;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_set_list_delim(cmark_node *node, cmark_delim_type delim) {
|
||||
if (!(delim == CMARK_PERIOD_DELIM || delim == CMARK_PAREN_DELIM)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
node->as.list.delimiter = delim;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_get_list_start(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
return node->as.list.start;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_set_list_start(cmark_node *node, int start) {
|
||||
if (node == NULL || start < 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
node->as.list.start = start;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_get_list_tight(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
return node->as.list.tight;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_set_list_tight(cmark_node *node, int tight) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_LIST) {
|
||||
node->as.list.tight = tight == 1;
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char *cmark_node_get_fence_info(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_CODE_BLOCK) {
|
||||
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.code.info);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_node_set_fence_info(cmark_node *node, const char *info) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (node->type == CMARK_NODE_CODE_BLOCK) {
|
||||
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.code.info, info);
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const char *cmark_node_get_url(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_LINK:
|
||||
case CMARK_NODE_IMAGE:
|
||||
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.link.url);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmark_node_set_url(cmark_node *node, const char *url) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_LINK:
|
||||
case CMARK_NODE_IMAGE:
|
||||
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.link.url, url);
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cmark_node_get_title(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_LINK:
|
||||
case CMARK_NODE_IMAGE:
|
||||
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.link.title);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmark_node_set_title(cmark_node *node, const char *title) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_LINK:
|
||||
case CMARK_NODE_IMAGE:
|
||||
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.link.title, title);
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cmark_node_get_on_enter(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.custom.on_enter);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmark_node_set_on_enter(cmark_node *node, const char *on_enter) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.custom.on_enter, on_enter);
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
const char *cmark_node_get_on_exit(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
return cmark_chunk_to_cstr(NODE_MEM(node), &node->as.custom.on_exit);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cmark_node_set_on_exit(cmark_node *node, const char *on_exit) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
cmark_chunk_set_cstr(NODE_MEM(node), &node->as.custom.on_exit, on_exit);
|
||||
return 1;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cmark_node_get_start_line(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return node->start_line;
|
||||
}
|
||||
|
||||
int cmark_node_get_start_column(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return node->start_column;
|
||||
}
|
||||
|
||||
int cmark_node_get_end_line(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return node->end_line;
|
||||
}
|
||||
|
||||
int cmark_node_get_end_column(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return 0;
|
||||
}
|
||||
return node->end_column;
|
||||
}
|
||||
|
||||
// Unlink a node without adjusting its next, prev, and parent pointers.
|
||||
static void S_node_unlink(cmark_node *node) {
|
||||
if (node == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node->prev) {
|
||||
node->prev->next = node->next;
|
||||
}
|
||||
if (node->next) {
|
||||
node->next->prev = node->prev;
|
||||
}
|
||||
|
||||
// Adjust first_child and last_child of parent.
|
||||
cmark_node *parent = node->parent;
|
||||
if (parent) {
|
||||
if (parent->first_child == node) {
|
||||
parent->first_child = node->next;
|
||||
}
|
||||
if (parent->last_child == node) {
|
||||
parent->last_child = node->prev;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cmark_node_unlink(cmark_node *node) {
|
||||
S_node_unlink(node);
|
||||
|
||||
node->next = NULL;
|
||||
node->prev = NULL;
|
||||
node->parent = NULL;
|
||||
}
|
||||
|
||||
int cmark_node_insert_before(cmark_node *node, cmark_node *sibling) {
|
||||
if (node == NULL || sibling == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!node->parent || !S_can_contain(node->parent, sibling)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
S_node_unlink(sibling);
|
||||
|
||||
cmark_node *old_prev = node->prev;
|
||||
|
||||
// Insert 'sibling' between 'old_prev' and 'node'.
|
||||
if (old_prev) {
|
||||
old_prev->next = sibling;
|
||||
}
|
||||
sibling->prev = old_prev;
|
||||
sibling->next = node;
|
||||
node->prev = sibling;
|
||||
|
||||
// Set new parent.
|
||||
cmark_node *parent = node->parent;
|
||||
sibling->parent = parent;
|
||||
|
||||
// Adjust first_child of parent if inserted as first child.
|
||||
if (parent && !old_prev) {
|
||||
parent->first_child = sibling;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cmark_node_insert_after(cmark_node *node, cmark_node *sibling) {
|
||||
if (node == NULL || sibling == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!node->parent || !S_can_contain(node->parent, sibling)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
S_node_unlink(sibling);
|
||||
|
||||
cmark_node *old_next = node->next;
|
||||
|
||||
// Insert 'sibling' between 'node' and 'old_next'.
|
||||
if (old_next) {
|
||||
old_next->prev = sibling;
|
||||
}
|
||||
sibling->next = old_next;
|
||||
sibling->prev = node;
|
||||
node->next = sibling;
|
||||
|
||||
// Set new parent.
|
||||
cmark_node *parent = node->parent;
|
||||
sibling->parent = parent;
|
||||
|
||||
// Adjust last_child of parent if inserted as last child.
|
||||
if (parent && !old_next) {
|
||||
parent->last_child = sibling;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cmark_node_replace(cmark_node *oldnode, cmark_node *newnode) {
|
||||
if (!cmark_node_insert_before(oldnode, newnode)) {
|
||||
return 0;
|
||||
}
|
||||
cmark_node_unlink(oldnode);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cmark_node_prepend_child(cmark_node *node, cmark_node *child) {
|
||||
if (!S_can_contain(node, child)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
S_node_unlink(child);
|
||||
|
||||
cmark_node *old_first_child = node->first_child;
|
||||
|
||||
child->next = old_first_child;
|
||||
child->prev = NULL;
|
||||
child->parent = node;
|
||||
node->first_child = child;
|
||||
|
||||
if (old_first_child) {
|
||||
old_first_child->prev = child;
|
||||
} else {
|
||||
// Also set last_child if node previously had no children.
|
||||
node->last_child = child;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int cmark_node_append_child(cmark_node *node, cmark_node *child) {
|
||||
if (!S_can_contain(node, child)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
S_node_unlink(child);
|
||||
|
||||
cmark_node *old_last_child = node->last_child;
|
||||
|
||||
child->next = NULL;
|
||||
child->prev = old_last_child;
|
||||
child->parent = node;
|
||||
node->last_child = child;
|
||||
|
||||
if (old_last_child) {
|
||||
old_last_child->next = child;
|
||||
} else {
|
||||
// Also set first_child if node previously had no children.
|
||||
node->first_child = child;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void S_print_error(FILE *out, cmark_node *node, const char *elem) {
|
||||
if (out == NULL) {
|
||||
return;
|
||||
}
|
||||
fprintf(out, "Invalid '%s' in node type %s at %d:%d\n", elem,
|
||||
cmark_node_get_type_string(node), node->start_line,
|
||||
node->start_column);
|
||||
}
|
||||
|
||||
int cmark_node_check(cmark_node *node, FILE *out) {
|
||||
cmark_node *cur;
|
||||
int errors = 0;
|
||||
|
||||
if (!node) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
cur = node;
|
||||
for (;;) {
|
||||
if (cur->first_child) {
|
||||
if (cur->first_child->prev != NULL) {
|
||||
S_print_error(out, cur->first_child, "prev");
|
||||
cur->first_child->prev = NULL;
|
||||
++errors;
|
||||
}
|
||||
if (cur->first_child->parent != cur) {
|
||||
S_print_error(out, cur->first_child, "parent");
|
||||
cur->first_child->parent = cur;
|
||||
++errors;
|
||||
}
|
||||
cur = cur->first_child;
|
||||
continue;
|
||||
}
|
||||
|
||||
next_sibling:
|
||||
if (cur == node) {
|
||||
break;
|
||||
}
|
||||
if (cur->next) {
|
||||
if (cur->next->prev != cur) {
|
||||
S_print_error(out, cur->next, "prev");
|
||||
cur->next->prev = cur;
|
||||
++errors;
|
||||
}
|
||||
if (cur->next->parent != cur->parent) {
|
||||
S_print_error(out, cur->next, "parent");
|
||||
cur->next->parent = cur->parent;
|
||||
++errors;
|
||||
}
|
||||
cur = cur->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cur->parent->last_child != cur) {
|
||||
S_print_error(out, cur->parent, "last_child");
|
||||
cur->parent->last_child = cur;
|
||||
++errors;
|
||||
}
|
||||
cur = cur->parent;
|
||||
goto next_sibling;
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
#ifndef CMARK_NODE_H
|
||||
#define CMARK_NODE_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cmark.h"
|
||||
#include "buffer.h"
|
||||
#include "chunk.h"
|
||||
|
||||
typedef struct {
|
||||
cmark_list_type list_type;
|
||||
int marker_offset;
|
||||
int padding;
|
||||
int start;
|
||||
cmark_delim_type delimiter;
|
||||
unsigned char bullet_char;
|
||||
bool tight;
|
||||
} cmark_list;
|
||||
|
||||
typedef struct {
|
||||
cmark_chunk info;
|
||||
cmark_chunk literal;
|
||||
uint8_t fence_length;
|
||||
uint8_t fence_offset;
|
||||
unsigned char fence_char;
|
||||
int8_t fenced;
|
||||
} cmark_code;
|
||||
|
||||
typedef struct {
|
||||
int level;
|
||||
bool setext;
|
||||
} cmark_heading;
|
||||
|
||||
typedef struct {
|
||||
cmark_chunk url;
|
||||
cmark_chunk title;
|
||||
} cmark_link;
|
||||
|
||||
typedef struct {
|
||||
cmark_chunk on_enter;
|
||||
cmark_chunk on_exit;
|
||||
} cmark_custom;
|
||||
|
||||
enum cmark_node__internal_flags {
|
||||
CMARK_NODE__OPEN = (1 << 0),
|
||||
CMARK_NODE__LAST_LINE_BLANK = (1 << 1),
|
||||
};
|
||||
|
||||
struct cmark_node {
|
||||
cmark_strbuf content;
|
||||
|
||||
struct cmark_node *next;
|
||||
struct cmark_node *prev;
|
||||
struct cmark_node *parent;
|
||||
struct cmark_node *first_child;
|
||||
struct cmark_node *last_child;
|
||||
|
||||
void *user_data;
|
||||
|
||||
int start_line;
|
||||
int start_column;
|
||||
int end_line;
|
||||
int end_column;
|
||||
int internal_offset;
|
||||
uint16_t type;
|
||||
uint16_t flags;
|
||||
|
||||
union {
|
||||
cmark_chunk literal;
|
||||
cmark_list list;
|
||||
cmark_code code;
|
||||
cmark_heading heading;
|
||||
cmark_link link;
|
||||
cmark_custom custom;
|
||||
int html_block_type;
|
||||
} as;
|
||||
};
|
||||
|
||||
static CMARK_INLINE cmark_mem *cmark_node_mem(cmark_node *node) {
|
||||
return node->content.mem;
|
||||
}
|
||||
CMARK_EXPORT int cmark_node_check(cmark_node *node, FILE *out);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,39 @@
|
|||
#ifndef CMARK_AST_H
|
||||
#define CMARK_AST_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include "node.h"
|
||||
#include "buffer.h"
|
||||
#include "memory.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MAX_LINK_LABEL_LENGTH 1000
|
||||
|
||||
struct cmark_parser {
|
||||
struct cmark_mem *mem;
|
||||
struct cmark_reference_map *refmap;
|
||||
struct cmark_node *root;
|
||||
struct cmark_node *current;
|
||||
int line_number;
|
||||
bufsize_t offset;
|
||||
bufsize_t column;
|
||||
bufsize_t first_nonspace;
|
||||
bufsize_t first_nonspace_column;
|
||||
int indent;
|
||||
bool blank;
|
||||
bool partially_consumed_tab;
|
||||
cmark_strbuf curline;
|
||||
bufsize_t last_line_length;
|
||||
cmark_strbuf linebuf;
|
||||
int options;
|
||||
bool last_buffer_ended_with_cr;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,146 @@
|
|||
#include "cmark.h"
|
||||
#include "utf8.h"
|
||||
#include "parser.h"
|
||||
#include "references.h"
|
||||
#include "inlines.h"
|
||||
#include "chunk.h"
|
||||
|
||||
static unsigned int refhash(const unsigned char *link_ref) {
|
||||
unsigned int hash = 0;
|
||||
|
||||
while (*link_ref)
|
||||
hash = (*link_ref++) + (hash << 6) + (hash << 16) - hash;
|
||||
|
||||
return hash;
|
||||
}
|
||||
|
||||
static void reference_free(cmark_reference_map *map, cmark_reference *ref) {
|
||||
cmark_mem *mem = map->mem;
|
||||
if (ref != NULL) {
|
||||
mem->free(ref->label);
|
||||
cmark_chunk_free(mem, &ref->url);
|
||||
cmark_chunk_free(mem, &ref->title);
|
||||
mem->free(ref);
|
||||
}
|
||||
}
|
||||
|
||||
// normalize reference: collapse internal whitespace to single space,
|
||||
// remove leading/trailing whitespace, case fold
|
||||
// Return NULL if the reference name is actually empty (i.e. composed
|
||||
// solely from whitespace)
|
||||
static unsigned char *normalize_reference(cmark_mem *mem, cmark_chunk *ref) {
|
||||
cmark_strbuf normalized = CMARK_BUF_INIT(mem);
|
||||
unsigned char *result;
|
||||
|
||||
if (ref == NULL)
|
||||
return NULL;
|
||||
|
||||
if (ref->len == 0)
|
||||
return NULL;
|
||||
|
||||
cmark_utf8proc_case_fold(&normalized, ref->data, ref->len);
|
||||
cmark_strbuf_trim(&normalized);
|
||||
cmark_strbuf_normalize_whitespace(&normalized);
|
||||
|
||||
result = cmark_strbuf_detach(&normalized);
|
||||
assert(result);
|
||||
|
||||
if (result[0] == '\0') {
|
||||
mem->free(result);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static void add_reference(cmark_reference_map *map, cmark_reference *ref) {
|
||||
cmark_reference *t = ref->next = map->table[ref->hash % REFMAP_SIZE];
|
||||
|
||||
while (t) {
|
||||
if (t->hash == ref->hash && !strcmp((char *)t->label, (char *)ref->label)) {
|
||||
reference_free(map, ref);
|
||||
return;
|
||||
}
|
||||
|
||||
t = t->next;
|
||||
}
|
||||
|
||||
map->table[ref->hash % REFMAP_SIZE] = ref;
|
||||
}
|
||||
|
||||
void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label,
|
||||
cmark_chunk *url, cmark_chunk *title) {
|
||||
cmark_reference *ref;
|
||||
unsigned char *reflabel = normalize_reference(map->mem, label);
|
||||
|
||||
/* empty reference name, or composed from only whitespace */
|
||||
if (reflabel == NULL)
|
||||
return;
|
||||
|
||||
ref = (cmark_reference *)map->mem->calloc(1, sizeof(*ref));
|
||||
ref->label = reflabel;
|
||||
ref->hash = refhash(ref->label);
|
||||
ref->url = cmark_clean_url(map->mem, url);
|
||||
ref->title = cmark_clean_title(map->mem, title);
|
||||
ref->next = NULL;
|
||||
|
||||
add_reference(map, ref);
|
||||
}
|
||||
|
||||
// Returns reference if refmap contains a reference with matching
|
||||
// label, otherwise NULL.
|
||||
cmark_reference *cmark_reference_lookup(cmark_reference_map *map,
|
||||
cmark_chunk *label) {
|
||||
cmark_reference *ref = NULL;
|
||||
unsigned char *norm;
|
||||
unsigned int hash;
|
||||
|
||||
if (label->len < 1 || label->len > MAX_LINK_LABEL_LENGTH)
|
||||
return NULL;
|
||||
|
||||
if (map == NULL)
|
||||
return NULL;
|
||||
|
||||
norm = normalize_reference(map->mem, label);
|
||||
if (norm == NULL)
|
||||
return NULL;
|
||||
|
||||
hash = refhash(norm);
|
||||
ref = map->table[hash % REFMAP_SIZE];
|
||||
|
||||
while (ref) {
|
||||
if (ref->hash == hash && !strcmp((char *)ref->label, (char *)norm))
|
||||
break;
|
||||
ref = ref->next;
|
||||
}
|
||||
|
||||
map->mem->free(norm);
|
||||
return ref;
|
||||
}
|
||||
|
||||
void cmark_reference_map_free(cmark_reference_map *map) {
|
||||
unsigned int i;
|
||||
|
||||
if (map == NULL)
|
||||
return;
|
||||
|
||||
for (i = 0; i < REFMAP_SIZE; ++i) {
|
||||
cmark_reference *ref = map->table[i];
|
||||
cmark_reference *next;
|
||||
|
||||
while (ref) {
|
||||
next = ref->next;
|
||||
reference_free(map, ref);
|
||||
ref = next;
|
||||
}
|
||||
}
|
||||
|
||||
map->mem->free(map);
|
||||
}
|
||||
|
||||
cmark_reference_map *cmark_reference_map_new(cmark_mem *mem) {
|
||||
cmark_reference_map *map =
|
||||
(cmark_reference_map *)mem->calloc(1, sizeof(cmark_reference_map));
|
||||
map->mem = mem;
|
||||
return map;
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
#ifndef CMARK_REFERENCES_H
|
||||
#define CMARK_REFERENCES_H
|
||||
|
||||
#include "memory.h"
|
||||
#include "chunk.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define REFMAP_SIZE 16
|
||||
|
||||
struct cmark_reference {
|
||||
struct cmark_reference *next;
|
||||
unsigned char *label;
|
||||
cmark_chunk url;
|
||||
cmark_chunk title;
|
||||
unsigned int hash;
|
||||
};
|
||||
|
||||
typedef struct cmark_reference cmark_reference;
|
||||
|
||||
struct cmark_reference_map {
|
||||
cmark_mem *mem;
|
||||
cmark_reference *table[REFMAP_SIZE];
|
||||
};
|
||||
|
||||
typedef struct cmark_reference_map cmark_reference_map;
|
||||
|
||||
cmark_reference_map *cmark_reference_map_new(cmark_mem *mem);
|
||||
void cmark_reference_map_free(cmark_reference_map *map);
|
||||
cmark_reference *cmark_reference_lookup(cmark_reference_map *map,
|
||||
cmark_chunk *label);
|
||||
extern void cmark_reference_create(cmark_reference_map *map, cmark_chunk *label,
|
||||
cmark_chunk *url, cmark_chunk *title);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,186 @@
|
|||
#include <stdlib.h>
|
||||
#include "buffer.h"
|
||||
#include "chunk.h"
|
||||
#include "cmark.h"
|
||||
#include "utf8.h"
|
||||
#include "render.h"
|
||||
#include "node.h"
|
||||
|
||||
static CMARK_INLINE void S_cr(cmark_renderer *renderer) {
|
||||
if (renderer->need_cr < 1) {
|
||||
renderer->need_cr = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static CMARK_INLINE void S_blankline(cmark_renderer *renderer) {
|
||||
if (renderer->need_cr < 2) {
|
||||
renderer->need_cr = 2;
|
||||
}
|
||||
}
|
||||
|
||||
static void S_out(cmark_renderer *renderer, const char *source, bool wrap,
|
||||
cmark_escaping escape) {
|
||||
int length = strlen(source);
|
||||
unsigned char nextc;
|
||||
int32_t c;
|
||||
int i = 0;
|
||||
int last_nonspace;
|
||||
int len;
|
||||
cmark_chunk remainder = cmark_chunk_literal("");
|
||||
int k = renderer->buffer->size - 1;
|
||||
|
||||
wrap = wrap && !renderer->no_linebreaks;
|
||||
|
||||
if (renderer->in_tight_list_item && renderer->need_cr > 1) {
|
||||
renderer->need_cr = 1;
|
||||
}
|
||||
while (renderer->need_cr) {
|
||||
if (k < 0 || renderer->buffer->ptr[k] == '\n') {
|
||||
k -= 1;
|
||||
} else {
|
||||
cmark_strbuf_putc(renderer->buffer, '\n');
|
||||
if (renderer->need_cr > 1) {
|
||||
cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
|
||||
renderer->prefix->size);
|
||||
}
|
||||
}
|
||||
renderer->column = 0;
|
||||
renderer->last_breakable = 0;
|
||||
renderer->begin_line = true;
|
||||
renderer->begin_content = true;
|
||||
renderer->need_cr -= 1;
|
||||
}
|
||||
|
||||
while (i < length) {
|
||||
if (renderer->begin_line) {
|
||||
cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
|
||||
renderer->prefix->size);
|
||||
// note: this assumes prefix is ascii:
|
||||
renderer->column = renderer->prefix->size;
|
||||
}
|
||||
|
||||
len = cmark_utf8proc_iterate((const uint8_t *)source + i, length - i, &c);
|
||||
if (len == -1) { // error condition
|
||||
return; // return without rendering rest of string
|
||||
}
|
||||
nextc = source[i + len];
|
||||
if (c == 32 && wrap) {
|
||||
if (!renderer->begin_line) {
|
||||
last_nonspace = renderer->buffer->size;
|
||||
cmark_strbuf_putc(renderer->buffer, ' ');
|
||||
renderer->column += 1;
|
||||
renderer->begin_line = false;
|
||||
renderer->begin_content = false;
|
||||
// skip following spaces
|
||||
while (source[i + 1] == ' ') {
|
||||
i++;
|
||||
}
|
||||
// We don't allow breaks that make a digit the first character
|
||||
// because this causes problems with commonmark output.
|
||||
if (!cmark_isdigit(source[i + 1])) {
|
||||
renderer->last_breakable = last_nonspace;
|
||||
}
|
||||
}
|
||||
|
||||
} else if (c == 10) {
|
||||
cmark_strbuf_putc(renderer->buffer, '\n');
|
||||
renderer->column = 0;
|
||||
renderer->begin_line = true;
|
||||
renderer->begin_content = true;
|
||||
renderer->last_breakable = 0;
|
||||
} else if (escape == LITERAL) {
|
||||
cmark_render_code_point(renderer, c);
|
||||
renderer->begin_line = false;
|
||||
// we don't set 'begin_content' to false til we've
|
||||
// finished parsing a digit. Reason: in commonmark
|
||||
// we need to escape a potential list marker after
|
||||
// a digit:
|
||||
renderer->begin_content =
|
||||
renderer->begin_content && cmark_isdigit(c) == 1;
|
||||
} else {
|
||||
(renderer->outc)(renderer, escape, c, nextc);
|
||||
renderer->begin_line = false;
|
||||
renderer->begin_content =
|
||||
renderer->begin_content && cmark_isdigit(c) == 1;
|
||||
}
|
||||
|
||||
// If adding the character went beyond width, look for an
|
||||
// earlier place where the line could be broken:
|
||||
if (renderer->width > 0 && renderer->column > renderer->width &&
|
||||
!renderer->begin_line && renderer->last_breakable > 0) {
|
||||
|
||||
// copy from last_breakable to remainder
|
||||
cmark_chunk_set_cstr(renderer->mem, &remainder,
|
||||
(char *)renderer->buffer->ptr +
|
||||
renderer->last_breakable + 1);
|
||||
// truncate at last_breakable
|
||||
cmark_strbuf_truncate(renderer->buffer, renderer->last_breakable);
|
||||
// add newline, prefix, and remainder
|
||||
cmark_strbuf_putc(renderer->buffer, '\n');
|
||||
cmark_strbuf_put(renderer->buffer, renderer->prefix->ptr,
|
||||
renderer->prefix->size);
|
||||
cmark_strbuf_put(renderer->buffer, remainder.data, remainder.len);
|
||||
renderer->column = renderer->prefix->size + remainder.len;
|
||||
cmark_chunk_free(renderer->mem, &remainder);
|
||||
renderer->last_breakable = 0;
|
||||
renderer->begin_line = false;
|
||||
renderer->begin_content = false;
|
||||
}
|
||||
|
||||
i += len;
|
||||
}
|
||||
}
|
||||
|
||||
// Assumes no newlines, assumes ascii content:
|
||||
void cmark_render_ascii(cmark_renderer *renderer, const char *s) {
|
||||
int origsize = renderer->buffer->size;
|
||||
cmark_strbuf_puts(renderer->buffer, s);
|
||||
renderer->column += renderer->buffer->size - origsize;
|
||||
}
|
||||
|
||||
void cmark_render_code_point(cmark_renderer *renderer, uint32_t c) {
|
||||
cmark_utf8proc_encode_char(c, renderer->buffer);
|
||||
renderer->column += 1;
|
||||
}
|
||||
|
||||
char *cmark_render(cmark_node *root, int options, int width,
|
||||
void (*outc)(cmark_renderer *, cmark_escaping, int32_t,
|
||||
unsigned char),
|
||||
int (*render_node)(cmark_renderer *renderer,
|
||||
cmark_node *node,
|
||||
cmark_event_type ev_type, int options)) {
|
||||
cmark_mem *mem = cmark_node_mem(root);
|
||||
cmark_strbuf pref = CMARK_BUF_INIT(mem);
|
||||
cmark_strbuf buf = CMARK_BUF_INIT(mem);
|
||||
cmark_node *cur;
|
||||
cmark_event_type ev_type;
|
||||
char *result;
|
||||
cmark_iter *iter = cmark_iter_new(root);
|
||||
|
||||
cmark_renderer renderer = {mem, &buf, &pref, 0, width,
|
||||
0, 0, true, true, false,
|
||||
false, outc, S_cr, S_blankline, S_out};
|
||||
|
||||
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
||||
cur = cmark_iter_get_node(iter);
|
||||
if (!render_node(&renderer, cur, ev_type, options)) {
|
||||
// a false value causes us to skip processing
|
||||
// the node's contents. this is used for
|
||||
// autolinks.
|
||||
cmark_iter_reset(iter, cur, CMARK_EVENT_EXIT);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure final newline
|
||||
if (renderer.buffer->ptr[renderer.buffer->size - 1] != '\n') {
|
||||
cmark_strbuf_putc(renderer.buffer, '\n');
|
||||
}
|
||||
|
||||
result = (char *)cmark_strbuf_detach(renderer.buffer);
|
||||
|
||||
cmark_iter_free(iter);
|
||||
cmark_strbuf_free(renderer.prefix);
|
||||
cmark_strbuf_free(renderer.buffer);
|
||||
|
||||
return result;
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
#ifndef CMARK_RENDER_H
|
||||
#define CMARK_RENDER_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "buffer.h"
|
||||
#include "chunk.h"
|
||||
#include "memory.h"
|
||||
|
||||
typedef enum { LITERAL, NORMAL, TITLE, URL } cmark_escaping;
|
||||
|
||||
struct cmark_renderer {
|
||||
cmark_mem *mem;
|
||||
cmark_strbuf *buffer;
|
||||
cmark_strbuf *prefix;
|
||||
int column;
|
||||
int width;
|
||||
int need_cr;
|
||||
bufsize_t last_breakable;
|
||||
bool begin_line;
|
||||
bool begin_content;
|
||||
bool no_linebreaks;
|
||||
bool in_tight_list_item;
|
||||
void (*outc)(struct cmark_renderer *, cmark_escaping, int32_t, unsigned char);
|
||||
void (*cr)(struct cmark_renderer *);
|
||||
void (*blankline)(struct cmark_renderer *);
|
||||
void (*out)(struct cmark_renderer *, const char *, bool, cmark_escaping);
|
||||
};
|
||||
|
||||
typedef struct cmark_renderer cmark_renderer;
|
||||
|
||||
void cmark_render_ascii(cmark_renderer *renderer, const char *s);
|
||||
|
||||
void cmark_render_code_point(cmark_renderer *renderer, uint32_t c);
|
||||
|
||||
char *cmark_render(cmark_node *root, int options, int width,
|
||||
void (*outc)(cmark_renderer *, cmark_escaping, int32_t,
|
||||
unsigned char),
|
||||
int (*render_node)(cmark_renderer *renderer,
|
||||
cmark_node *node,
|
||||
cmark_event_type ev_type, int options));
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,55 @@
|
|||
#include "cmark.h"
|
||||
#include "chunk.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c,
|
||||
bufsize_t offset);
|
||||
bufsize_t _scan_scheme(const unsigned char *p);
|
||||
bufsize_t _scan_autolink_uri(const unsigned char *p);
|
||||
bufsize_t _scan_autolink_email(const unsigned char *p);
|
||||
bufsize_t _scan_html_tag(const unsigned char *p);
|
||||
bufsize_t _scan_html_block_start(const unsigned char *p);
|
||||
bufsize_t _scan_html_block_start_7(const unsigned char *p);
|
||||
bufsize_t _scan_html_block_end_1(const unsigned char *p);
|
||||
bufsize_t _scan_html_block_end_2(const unsigned char *p);
|
||||
bufsize_t _scan_html_block_end_3(const unsigned char *p);
|
||||
bufsize_t _scan_html_block_end_4(const unsigned char *p);
|
||||
bufsize_t _scan_html_block_end_5(const unsigned char *p);
|
||||
bufsize_t _scan_link_title(const unsigned char *p);
|
||||
bufsize_t _scan_spacechars(const unsigned char *p);
|
||||
bufsize_t _scan_atx_heading_start(const unsigned char *p);
|
||||
bufsize_t _scan_setext_heading_line(const unsigned char *p);
|
||||
bufsize_t _scan_thematic_break(const unsigned char *p);
|
||||
bufsize_t _scan_open_code_fence(const unsigned char *p);
|
||||
bufsize_t _scan_close_code_fence(const unsigned char *p);
|
||||
bufsize_t _scan_entity(const unsigned char *p);
|
||||
bufsize_t _scan_dangerous_url(const unsigned char *p);
|
||||
|
||||
#define scan_scheme(c, n) _scan_at(&_scan_scheme, c, n)
|
||||
#define scan_autolink_uri(c, n) _scan_at(&_scan_autolink_uri, c, n)
|
||||
#define scan_autolink_email(c, n) _scan_at(&_scan_autolink_email, c, n)
|
||||
#define scan_html_tag(c, n) _scan_at(&_scan_html_tag, c, n)
|
||||
#define scan_html_block_start(c, n) _scan_at(&_scan_html_block_start, c, n)
|
||||
#define scan_html_block_start_7(c, n) _scan_at(&_scan_html_block_start_7, c, n)
|
||||
#define scan_html_block_end_1(c, n) _scan_at(&_scan_html_block_end_1, c, n)
|
||||
#define scan_html_block_end_2(c, n) _scan_at(&_scan_html_block_end_2, c, n)
|
||||
#define scan_html_block_end_3(c, n) _scan_at(&_scan_html_block_end_3, c, n)
|
||||
#define scan_html_block_end_4(c, n) _scan_at(&_scan_html_block_end_4, c, n)
|
||||
#define scan_html_block_end_5(c, n) _scan_at(&_scan_html_block_end_5, c, n)
|
||||
#define scan_link_title(c, n) _scan_at(&_scan_link_title, c, n)
|
||||
#define scan_spacechars(c, n) _scan_at(&_scan_spacechars, c, n)
|
||||
#define scan_atx_heading_start(c, n) _scan_at(&_scan_atx_heading_start, c, n)
|
||||
#define scan_setext_heading_line(c, n) \
|
||||
_scan_at(&_scan_setext_heading_line, c, n)
|
||||
#define scan_thematic_break(c, n) _scan_at(&_scan_thematic_break, c, n)
|
||||
#define scan_open_code_fence(c, n) _scan_at(&_scan_open_code_fence, c, n)
|
||||
#define scan_close_code_fence(c, n) _scan_at(&_scan_close_code_fence, c, n)
|
||||
#define scan_entity(c, n) _scan_at(&_scan_entity, c, n)
|
||||
#define scan_dangerous_url(c, n) _scan_at(&_scan_dangerous_url, c, n)
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,320 @@
|
|||
#include <stdlib.h>
|
||||
#include "chunk.h"
|
||||
#include "scanners.h"
|
||||
|
||||
bufsize_t _scan_at(bufsize_t (*scanner)(const unsigned char *), cmark_chunk *c, bufsize_t offset)
|
||||
{
|
||||
bufsize_t res;
|
||||
unsigned char *ptr = (unsigned char *)c->data;
|
||||
|
||||
if (ptr == NULL || offset > c->len) {
|
||||
return 0;
|
||||
} else {
|
||||
unsigned char lim = ptr[c->len];
|
||||
|
||||
ptr[c->len] = '\0';
|
||||
res = scanner(ptr + offset);
|
||||
ptr[c->len] = lim;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/*!re2c
|
||||
re2c:define:YYCTYPE = "unsigned char";
|
||||
re2c:define:YYCURSOR = p;
|
||||
re2c:define:YYMARKER = marker;
|
||||
re2c:define:YYCTXMARKER = marker;
|
||||
re2c:yyfill:enable = 0;
|
||||
|
||||
wordchar = [^\x00-\x20];
|
||||
|
||||
spacechar = [ \t\v\f\r\n];
|
||||
|
||||
reg_char = [^\\()\x00-\x20];
|
||||
|
||||
escaped_char = [\\][!"#$%&'()*+,./:;<=>?@[\\\]^_`{|}~-];
|
||||
|
||||
tagname = [A-Za-z][A-Za-z0-9-]*;
|
||||
|
||||
blocktagname = 'address'|'article'|'aside'|'base'|'basefont'|'blockquote'|'body'|'caption'|'center'|'col'|'colgroup'|'dd'|'details'|'dialog'|'dir'|'div'|'dl'|'dt'|'fieldset'|'figcaption'|'figure'|'footer'|'form'|'frame'|'frameset'|'h1'|'h2'|'h3'|'h4'|'h5'|'h6'|'head'|'header'|'hr'|'html'|'iframe'|'legend'|'li'|'link'|'main'|'menu'|'menuitem'|'nav'|'noframes'|'ol'|'optgroup'|'option'|'p'|'param'|'section'|'source'|'title'|'summary'|'table'|'tbody'|'td'|'tfoot'|'th'|'thead'|'title'|'tr'|'track'|'ul';
|
||||
|
||||
attributename = [a-zA-Z_:][a-zA-Z0-9:._-]*;
|
||||
|
||||
unquotedvalue = [^ \t\r\n\v\f"'=<>`\x00]+;
|
||||
singlequotedvalue = ['][^'\x00]*['];
|
||||
doublequotedvalue = ["][^"\x00]*["];
|
||||
|
||||
attributevalue = unquotedvalue | singlequotedvalue | doublequotedvalue;
|
||||
|
||||
attributevaluespec = spacechar* [=] spacechar* attributevalue;
|
||||
|
||||
attribute = spacechar+ attributename attributevaluespec?;
|
||||
|
||||
opentag = tagname attribute* spacechar* [/]? [>];
|
||||
closetag = [/] tagname spacechar* [>];
|
||||
|
||||
htmlcomment = "!---->" | ("!--" ([-]? [^\x00>-]) ([-]? [^\x00-])* "-->");
|
||||
|
||||
processinginstruction = "?" ([^?>\x00]+ | [?][^>\x00] | [>])* "?>";
|
||||
|
||||
declaration = "!" [A-Z]+ spacechar+ [^>\x00]* ">";
|
||||
|
||||
cdata = "![CDATA[" ([^\]\x00]+ | "]" [^\]\x00] | "]]" [^>\x00])* "]]>";
|
||||
|
||||
htmltag = opentag | closetag | htmlcomment | processinginstruction |
|
||||
declaration | cdata;
|
||||
|
||||
in_parens_nosp = [(] (reg_char|escaped_char|[\\])* [)];
|
||||
|
||||
in_double_quotes = ["] (escaped_char|[^"\x00])* ["];
|
||||
in_single_quotes = ['] (escaped_char|[^'\x00])* ['];
|
||||
in_parens = [(] (escaped_char|[^)\x00])* [)];
|
||||
|
||||
scheme = [A-Za-z][A-Za-z0-9.+-]{1,31};
|
||||
*/
|
||||
|
||||
// Try to match a scheme including colon.
|
||||
bufsize_t _scan_scheme(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
scheme [:] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match URI autolink after first <, returning number of chars matched.
|
||||
bufsize_t _scan_autolink_uri(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
scheme [:][^\x00-\x20<>]*[>] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match email autolink after first <, returning num of chars matched.
|
||||
bufsize_t _scan_autolink_email(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+
|
||||
[@]
|
||||
[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?
|
||||
([.][a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*
|
||||
[>] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML tag after first <, returning num of chars matched.
|
||||
bufsize_t _scan_html_tag(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
htmltag { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML block tag start line, returning
|
||||
// an integer code for the type of block (1-6, matching the spec).
|
||||
// #7 is handled by a separate function, below.
|
||||
bufsize_t _scan_html_block_start(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
/*!re2c
|
||||
[<] ('script'|'pre'|'style') (spacechar | [>]) { return 1; }
|
||||
'<!--' { return 2; }
|
||||
'<?' { return 3; }
|
||||
'<!' [A-Z] { return 4; }
|
||||
'<![CDATA[' { return 5; }
|
||||
[<] [/]? blocktagname (spacechar | [/]? [>]) { return 6; }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML block tag start line of type 7, returning
|
||||
// 7 if successful, 0 if not.
|
||||
bufsize_t _scan_html_block_start_7(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
/*!re2c
|
||||
[<] (opentag | closetag) [\t\n\f ]* [\r\n] { return 7; }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML block end line of type 1
|
||||
bufsize_t _scan_html_block_end_1(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[^\n\x00]* [<] [/] ('script'|'pre'|'style') [>] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML block end line of type 2
|
||||
bufsize_t _scan_html_block_end_2(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[^\n\x00]* '-->' { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML block end line of type 3
|
||||
bufsize_t _scan_html_block_end_3(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[^\n\x00]* '?>' { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML block end line of type 4
|
||||
bufsize_t _scan_html_block_end_4(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[^\n\x00]* '>' { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match an HTML block end line of type 5
|
||||
bufsize_t _scan_html_block_end_5(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[^\n\x00]* ']]>' { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Try to match a link title (in single quotes, in double quotes, or
|
||||
// in parentheses), returning number of chars matched. Allow one
|
||||
// level of internal nesting (quotes within quotes).
|
||||
bufsize_t _scan_link_title(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
["] (escaped_char|[^"\x00])* ["] { return (bufsize_t)(p - start); }
|
||||
['] (escaped_char|[^'\x00])* ['] { return (bufsize_t)(p - start); }
|
||||
[(] (escaped_char|[^)\x00])* [)] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Match space characters, including newlines.
|
||||
bufsize_t _scan_spacechars(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *start = p; \
|
||||
/*!re2c
|
||||
[ \t\v\f\r\n]+ { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Match ATX heading start.
|
||||
bufsize_t _scan_atx_heading_start(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[#]{1,6} ([ \t]+|[\r\n]) { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Match setext heading line. Return 1 for level-1 heading,
|
||||
// 2 for level-2, 0 for no match.
|
||||
bufsize_t _scan_setext_heading_line(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
/*!re2c
|
||||
[=]+ [ \t]* [\r\n] { return 1; }
|
||||
[-]+ [ \t]* [\r\n] { return 2; }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Scan a thematic break line: "...three or more hyphens, asterisks,
|
||||
// or underscores on a line by themselves. If you wish, you may use
|
||||
// spaces between the hyphens or asterisks."
|
||||
bufsize_t _scan_thematic_break(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
([*][ \t]*){3,} [ \t]* [\r\n] { return (bufsize_t)(p - start); }
|
||||
([_][ \t]*){3,} [ \t]* [\r\n] { return (bufsize_t)(p - start); }
|
||||
([-][ \t]*){3,} [ \t]* [\r\n] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Scan an opening code fence.
|
||||
bufsize_t _scan_open_code_fence(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[`]{3,} / [^`\r\n\x00]*[\r\n] { return (bufsize_t)(p - start); }
|
||||
[~]{3,} / [^\r\n\x00]*[\r\n] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Scan a closing code fence with length at least len.
|
||||
bufsize_t _scan_close_code_fence(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[`]{3,} / [ \t]*[\r\n] { return (bufsize_t)(p - start); }
|
||||
[~]{3,} / [ \t]*[\r\n] { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Scans an entity.
|
||||
// Returns number of chars matched.
|
||||
bufsize_t _scan_entity(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
[&] ([#] ([Xx][A-Fa-f0-9]{1,6}|[0-9]{1,7}) |[A-Za-z][A-Za-z0-9]{1,31} ) [;]
|
||||
{ return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
||||
// Returns positive value if a URL begins in a way that is potentially
|
||||
// dangerous, with javascript:, vbscript:, file:, or data:, otherwise 0.
|
||||
bufsize_t _scan_dangerous_url(const unsigned char *p)
|
||||
{
|
||||
const unsigned char *marker = NULL;
|
||||
const unsigned char *start = p;
|
||||
/*!re2c
|
||||
'data:image/' ('png'|'gif'|'jpeg'|'webp') { return 0; }
|
||||
'javascript:' | 'vbscript:' | 'file:' | 'data:' { return (bufsize_t)(p - start); }
|
||||
* { return 0; }
|
||||
*/
|
||||
}
|
||||
|
|
@ -0,0 +1,317 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "cmark_ctype.h"
|
||||
#include "utf8.h"
|
||||
|
||||
static const int8_t utf8proc_utf8class[256] = {
|
||||
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, 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, 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, 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, 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,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
|
||||
2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
|
||||
4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0};
|
||||
|
||||
static void encode_unknown(cmark_strbuf *buf) {
|
||||
static const uint8_t repl[] = {239, 191, 189};
|
||||
cmark_strbuf_put(buf, repl, 3);
|
||||
}
|
||||
|
||||
static int utf8proc_charlen(const uint8_t *str, bufsize_t str_len) {
|
||||
int length, i;
|
||||
|
||||
if (!str_len)
|
||||
return 0;
|
||||
|
||||
length = utf8proc_utf8class[str[0]];
|
||||
|
||||
if (!length)
|
||||
return -1;
|
||||
|
||||
if (str_len >= 0 && (bufsize_t)length > str_len)
|
||||
return -str_len;
|
||||
|
||||
for (i = 1; i < length; i++) {
|
||||
if ((str[i] & 0xC0) != 0x80)
|
||||
return -i;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
// Validate a single UTF-8 character according to RFC 3629.
|
||||
static int utf8proc_valid(const uint8_t *str, bufsize_t str_len) {
|
||||
int length = utf8proc_utf8class[str[0]];
|
||||
|
||||
if (!length)
|
||||
return -1;
|
||||
|
||||
if ((bufsize_t)length > str_len)
|
||||
return -str_len;
|
||||
|
||||
switch (length) {
|
||||
case 2:
|
||||
if ((str[1] & 0xC0) != 0x80)
|
||||
return -1;
|
||||
if (str[0] < 0xC2) {
|
||||
// Overlong
|
||||
return -length;
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if ((str[1] & 0xC0) != 0x80)
|
||||
return -1;
|
||||
if ((str[2] & 0xC0) != 0x80)
|
||||
return -2;
|
||||
if (str[0] == 0xE0) {
|
||||
if (str[1] < 0xA0) {
|
||||
// Overlong
|
||||
return -length;
|
||||
}
|
||||
} else if (str[0] == 0xED) {
|
||||
if (str[1] >= 0xA0) {
|
||||
// Surrogate
|
||||
return -length;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case 4:
|
||||
if ((str[1] & 0xC0) != 0x80)
|
||||
return -1;
|
||||
if ((str[2] & 0xC0) != 0x80)
|
||||
return -2;
|
||||
if ((str[3] & 0xC0) != 0x80)
|
||||
return -3;
|
||||
if (str[0] == 0xF0) {
|
||||
if (str[1] < 0x90) {
|
||||
// Overlong
|
||||
return -length;
|
||||
}
|
||||
} else if (str[0] >= 0xF4) {
|
||||
if (str[0] > 0xF4 || str[1] >= 0x90) {
|
||||
// Above 0x10FFFF
|
||||
return -length;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
void cmark_utf8proc_check(cmark_strbuf *ob, const uint8_t *line,
|
||||
bufsize_t size) {
|
||||
bufsize_t i = 0;
|
||||
|
||||
while (i < size) {
|
||||
bufsize_t org = i;
|
||||
int charlen = 0;
|
||||
|
||||
while (i < size) {
|
||||
if (line[i] < 0x80 && line[i] != 0) {
|
||||
i++;
|
||||
} else if (line[i] >= 0x80) {
|
||||
charlen = utf8proc_valid(line + i, size - i);
|
||||
if (charlen < 0) {
|
||||
charlen = -charlen;
|
||||
break;
|
||||
}
|
||||
i += charlen;
|
||||
} else if (line[i] == 0) {
|
||||
// ASCII NUL is technically valid but rejected
|
||||
// for security reasons.
|
||||
charlen = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i > org) {
|
||||
cmark_strbuf_put(ob, line + org, i - org);
|
||||
}
|
||||
|
||||
if (i >= size) {
|
||||
break;
|
||||
} else {
|
||||
// Invalid UTF-8
|
||||
encode_unknown(ob);
|
||||
i += charlen;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len,
|
||||
int32_t *dst) {
|
||||
int length;
|
||||
int32_t uc = -1;
|
||||
|
||||
*dst = -1;
|
||||
length = utf8proc_charlen(str, str_len);
|
||||
if (length < 0)
|
||||
return -1;
|
||||
|
||||
switch (length) {
|
||||
case 1:
|
||||
uc = str[0];
|
||||
break;
|
||||
case 2:
|
||||
uc = ((str[0] & 0x1F) << 6) + (str[1] & 0x3F);
|
||||
if (uc < 0x80)
|
||||
uc = -1;
|
||||
break;
|
||||
case 3:
|
||||
uc = ((str[0] & 0x0F) << 12) + ((str[1] & 0x3F) << 6) + (str[2] & 0x3F);
|
||||
if (uc < 0x800 || (uc >= 0xD800 && uc < 0xE000))
|
||||
uc = -1;
|
||||
break;
|
||||
case 4:
|
||||
uc = ((str[0] & 0x07) << 18) + ((str[1] & 0x3F) << 12) +
|
||||
((str[2] & 0x3F) << 6) + (str[3] & 0x3F);
|
||||
if (uc < 0x10000 || uc >= 0x110000)
|
||||
uc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (uc < 0)
|
||||
return -1;
|
||||
|
||||
*dst = uc;
|
||||
return length;
|
||||
}
|
||||
|
||||
void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf) {
|
||||
uint8_t dst[4];
|
||||
bufsize_t len = 0;
|
||||
|
||||
assert(uc >= 0);
|
||||
|
||||
if (uc < 0x80) {
|
||||
dst[0] = (uint8_t)(uc);
|
||||
len = 1;
|
||||
} else if (uc < 0x800) {
|
||||
dst[0] = (uint8_t)(0xC0 + (uc >> 6));
|
||||
dst[1] = 0x80 + (uc & 0x3F);
|
||||
len = 2;
|
||||
} else if (uc == 0xFFFF) {
|
||||
dst[0] = 0xFF;
|
||||
len = 1;
|
||||
} else if (uc == 0xFFFE) {
|
||||
dst[0] = 0xFE;
|
||||
len = 1;
|
||||
} else if (uc < 0x10000) {
|
||||
dst[0] = (uint8_t)(0xE0 + (uc >> 12));
|
||||
dst[1] = 0x80 + ((uc >> 6) & 0x3F);
|
||||
dst[2] = 0x80 + (uc & 0x3F);
|
||||
len = 3;
|
||||
} else if (uc < 0x110000) {
|
||||
dst[0] = (uint8_t)(0xF0 + (uc >> 18));
|
||||
dst[1] = 0x80 + ((uc >> 12) & 0x3F);
|
||||
dst[2] = 0x80 + ((uc >> 6) & 0x3F);
|
||||
dst[3] = 0x80 + (uc & 0x3F);
|
||||
len = 4;
|
||||
} else {
|
||||
encode_unknown(buf);
|
||||
return;
|
||||
}
|
||||
|
||||
cmark_strbuf_put(buf, dst, len);
|
||||
}
|
||||
|
||||
void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str,
|
||||
bufsize_t len) {
|
||||
int32_t c;
|
||||
|
||||
#define bufpush(x) cmark_utf8proc_encode_char(x, dest)
|
||||
|
||||
while (len > 0) {
|
||||
bufsize_t char_len = cmark_utf8proc_iterate(str, len, &c);
|
||||
|
||||
if (char_len >= 0) {
|
||||
#include "case_fold_switch.inc"
|
||||
} else {
|
||||
encode_unknown(dest);
|
||||
char_len = -char_len;
|
||||
}
|
||||
|
||||
str += char_len;
|
||||
len -= char_len;
|
||||
}
|
||||
}
|
||||
|
||||
// matches anything in the Zs class, plus LF, CR, TAB, FF.
|
||||
int cmark_utf8proc_is_space(int32_t uc) {
|
||||
return (uc == 9 || uc == 10 || uc == 12 || uc == 13 || uc == 32 ||
|
||||
uc == 160 || uc == 5760 || (uc >= 8192 && uc <= 8202) || uc == 8239 ||
|
||||
uc == 8287 || uc == 12288);
|
||||
}
|
||||
|
||||
// matches anything in the P[cdefios] classes.
|
||||
int cmark_utf8proc_is_punctuation(int32_t uc) {
|
||||
return (
|
||||
(uc < 128 && cmark_ispunct((char)uc)) || uc == 161 || uc == 167 ||
|
||||
uc == 171 || uc == 182 || uc == 183 || uc == 187 || uc == 191 ||
|
||||
uc == 894 || uc == 903 || (uc >= 1370 && uc <= 1375) || uc == 1417 ||
|
||||
uc == 1418 || uc == 1470 || uc == 1472 || uc == 1475 || uc == 1478 ||
|
||||
uc == 1523 || uc == 1524 || uc == 1545 || uc == 1546 || uc == 1548 ||
|
||||
uc == 1549 || uc == 1563 || uc == 1566 || uc == 1567 ||
|
||||
(uc >= 1642 && uc <= 1645) || uc == 1748 || (uc >= 1792 && uc <= 1805) ||
|
||||
(uc >= 2039 && uc <= 2041) || (uc >= 2096 && uc <= 2110) || uc == 2142 ||
|
||||
uc == 2404 || uc == 2405 || uc == 2416 || uc == 2800 || uc == 3572 ||
|
||||
uc == 3663 || uc == 3674 || uc == 3675 || (uc >= 3844 && uc <= 3858) ||
|
||||
uc == 3860 || (uc >= 3898 && uc <= 3901) || uc == 3973 ||
|
||||
(uc >= 4048 && uc <= 4052) || uc == 4057 || uc == 4058 ||
|
||||
(uc >= 4170 && uc <= 4175) || uc == 4347 || (uc >= 4960 && uc <= 4968) ||
|
||||
uc == 5120 || uc == 5741 || uc == 5742 || uc == 5787 || uc == 5788 ||
|
||||
(uc >= 5867 && uc <= 5869) || uc == 5941 || uc == 5942 ||
|
||||
(uc >= 6100 && uc <= 6102) || (uc >= 6104 && uc <= 6106) ||
|
||||
(uc >= 6144 && uc <= 6154) || uc == 6468 || uc == 6469 || uc == 6686 ||
|
||||
uc == 6687 || (uc >= 6816 && uc <= 6822) || (uc >= 6824 && uc <= 6829) ||
|
||||
(uc >= 7002 && uc <= 7008) || (uc >= 7164 && uc <= 7167) ||
|
||||
(uc >= 7227 && uc <= 7231) || uc == 7294 || uc == 7295 ||
|
||||
(uc >= 7360 && uc <= 7367) || uc == 7379 || (uc >= 8208 && uc <= 8231) ||
|
||||
(uc >= 8240 && uc <= 8259) || (uc >= 8261 && uc <= 8273) ||
|
||||
(uc >= 8275 && uc <= 8286) || uc == 8317 || uc == 8318 || uc == 8333 ||
|
||||
uc == 8334 || (uc >= 8968 && uc <= 8971) || uc == 9001 || uc == 9002 ||
|
||||
(uc >= 10088 && uc <= 10101) || uc == 10181 || uc == 10182 ||
|
||||
(uc >= 10214 && uc <= 10223) || (uc >= 10627 && uc <= 10648) ||
|
||||
(uc >= 10712 && uc <= 10715) || uc == 10748 || uc == 10749 ||
|
||||
(uc >= 11513 && uc <= 11516) || uc == 11518 || uc == 11519 ||
|
||||
uc == 11632 || (uc >= 11776 && uc <= 11822) ||
|
||||
(uc >= 11824 && uc <= 11842) || (uc >= 12289 && uc <= 12291) ||
|
||||
(uc >= 12296 && uc <= 12305) || (uc >= 12308 && uc <= 12319) ||
|
||||
uc == 12336 || uc == 12349 || uc == 12448 || uc == 12539 || uc == 42238 ||
|
||||
uc == 42239 || (uc >= 42509 && uc <= 42511) || uc == 42611 ||
|
||||
uc == 42622 || (uc >= 42738 && uc <= 42743) ||
|
||||
(uc >= 43124 && uc <= 43127) || uc == 43214 || uc == 43215 ||
|
||||
(uc >= 43256 && uc <= 43258) || uc == 43310 || uc == 43311 ||
|
||||
uc == 43359 || (uc >= 43457 && uc <= 43469) || uc == 43486 ||
|
||||
uc == 43487 || (uc >= 43612 && uc <= 43615) || uc == 43742 ||
|
||||
uc == 43743 || uc == 43760 || uc == 43761 || uc == 44011 || uc == 64830 ||
|
||||
uc == 64831 || (uc >= 65040 && uc <= 65049) ||
|
||||
(uc >= 65072 && uc <= 65106) || (uc >= 65108 && uc <= 65121) ||
|
||||
uc == 65123 || uc == 65128 || uc == 65130 || uc == 65131 ||
|
||||
(uc >= 65281 && uc <= 65283) || (uc >= 65285 && uc <= 65290) ||
|
||||
(uc >= 65292 && uc <= 65295) || uc == 65306 || uc == 65307 ||
|
||||
uc == 65311 || uc == 65312 || (uc >= 65339 && uc <= 65341) ||
|
||||
uc == 65343 || uc == 65371 || uc == 65373 ||
|
||||
(uc >= 65375 && uc <= 65381) || (uc >= 65792 && uc <= 65794) ||
|
||||
uc == 66463 || uc == 66512 || uc == 66927 || uc == 67671 || uc == 67871 ||
|
||||
uc == 67903 || (uc >= 68176 && uc <= 68184) || uc == 68223 ||
|
||||
(uc >= 68336 && uc <= 68342) || (uc >= 68409 && uc <= 68415) ||
|
||||
(uc >= 68505 && uc <= 68508) || (uc >= 69703 && uc <= 69709) ||
|
||||
uc == 69819 || uc == 69820 || (uc >= 69822 && uc <= 69825) ||
|
||||
(uc >= 69952 && uc <= 69955) || uc == 70004 || uc == 70005 ||
|
||||
(uc >= 70085 && uc <= 70088) || uc == 70093 ||
|
||||
(uc >= 70200 && uc <= 70205) || uc == 70854 ||
|
||||
(uc >= 71105 && uc <= 71113) || (uc >= 71233 && uc <= 71235) ||
|
||||
(uc >= 74864 && uc <= 74868) || uc == 92782 || uc == 92783 ||
|
||||
uc == 92917 || (uc >= 92983 && uc <= 92987) || uc == 92996 ||
|
||||
uc == 113823);
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
#ifndef CMARK_UTF8_H
|
||||
#define CMARK_UTF8_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "buffer.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
void cmark_utf8proc_case_fold(cmark_strbuf *dest, const uint8_t *str,
|
||||
bufsize_t len);
|
||||
void cmark_utf8proc_encode_char(int32_t uc, cmark_strbuf *buf);
|
||||
int cmark_utf8proc_iterate(const uint8_t *str, bufsize_t str_len, int32_t *dst);
|
||||
void cmark_utf8proc_check(cmark_strbuf *dest, const uint8_t *line,
|
||||
bufsize_t size);
|
||||
int cmark_utf8proc_is_space(int32_t uc);
|
||||
int cmark_utf8proc_is_punctuation(int32_t uc);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
|
@ -0,0 +1,170 @@
|
|||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "cmark.h"
|
||||
#include "node.h"
|
||||
#include "buffer.h"
|
||||
#include "houdini.h"
|
||||
|
||||
#define BUFFER_SIZE 100
|
||||
|
||||
// Functions to convert cmark_nodes to XML strings.
|
||||
|
||||
static void escape_xml(cmark_strbuf *dest, const unsigned char *source,
|
||||
bufsize_t length) {
|
||||
houdini_escape_html0(dest, source, length, 0);
|
||||
}
|
||||
|
||||
struct render_state {
|
||||
cmark_strbuf *xml;
|
||||
int indent;
|
||||
};
|
||||
|
||||
static CMARK_INLINE void indent(struct render_state *state) {
|
||||
int i;
|
||||
for (i = 0; i < state->indent; i++) {
|
||||
cmark_strbuf_putc(state->xml, ' ');
|
||||
}
|
||||
}
|
||||
|
||||
static int S_render_node(cmark_node *node, cmark_event_type ev_type,
|
||||
struct render_state *state, int options) {
|
||||
cmark_strbuf *xml = state->xml;
|
||||
bool literal = false;
|
||||
cmark_delim_type delim;
|
||||
bool entering = (ev_type == CMARK_EVENT_ENTER);
|
||||
char buffer[BUFFER_SIZE];
|
||||
|
||||
if (entering) {
|
||||
indent(state);
|
||||
cmark_strbuf_putc(xml, '<');
|
||||
cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
|
||||
|
||||
if (options & CMARK_OPT_SOURCEPOS && node->start_line != 0) {
|
||||
snprintf(buffer, BUFFER_SIZE, " sourcepos=\"%d:%d-%d:%d\"",
|
||||
node->start_line, node->start_column, node->end_line,
|
||||
node->end_column);
|
||||
cmark_strbuf_puts(xml, buffer);
|
||||
}
|
||||
|
||||
literal = false;
|
||||
|
||||
switch (node->type) {
|
||||
case CMARK_NODE_DOCUMENT:
|
||||
cmark_strbuf_puts(xml, " xmlns=\"http://commonmark.org/xml/1.0\"");
|
||||
break;
|
||||
case CMARK_NODE_TEXT:
|
||||
case CMARK_NODE_CODE:
|
||||
case CMARK_NODE_HTML_BLOCK:
|
||||
case CMARK_NODE_HTML_INLINE:
|
||||
cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
|
||||
escape_xml(xml, node->as.literal.data, node->as.literal.len);
|
||||
cmark_strbuf_puts(xml, "</");
|
||||
cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
|
||||
literal = true;
|
||||
break;
|
||||
case CMARK_NODE_LIST:
|
||||
switch (cmark_node_get_list_type(node)) {
|
||||
case CMARK_ORDERED_LIST:
|
||||
cmark_strbuf_puts(xml, " type=\"ordered\"");
|
||||
snprintf(buffer, BUFFER_SIZE, " start=\"%d\"",
|
||||
cmark_node_get_list_start(node));
|
||||
cmark_strbuf_puts(xml, buffer);
|
||||
delim = cmark_node_get_list_delim(node);
|
||||
if (delim == CMARK_PAREN_DELIM) {
|
||||
cmark_strbuf_puts(xml, " delim=\"paren\"");
|
||||
} else if (delim == CMARK_PERIOD_DELIM) {
|
||||
cmark_strbuf_puts(xml, " delim=\"period\"");
|
||||
}
|
||||
break;
|
||||
case CMARK_BULLET_LIST:
|
||||
cmark_strbuf_puts(xml, " type=\"bullet\"");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
snprintf(buffer, BUFFER_SIZE, " tight=\"%s\"",
|
||||
(cmark_node_get_list_tight(node) ? "true" : "false"));
|
||||
cmark_strbuf_puts(xml, buffer);
|
||||
break;
|
||||
case CMARK_NODE_HEADING:
|
||||
snprintf(buffer, BUFFER_SIZE, " level=\"%d\"", node->as.heading.level);
|
||||
cmark_strbuf_puts(xml, buffer);
|
||||
break;
|
||||
case CMARK_NODE_CODE_BLOCK:
|
||||
if (node->as.code.info.len > 0) {
|
||||
cmark_strbuf_puts(xml, " info=\"");
|
||||
escape_xml(xml, node->as.code.info.data, node->as.code.info.len);
|
||||
cmark_strbuf_putc(xml, '"');
|
||||
}
|
||||
cmark_strbuf_puts(xml, " xml:space=\"preserve\">");
|
||||
escape_xml(xml, node->as.code.literal.data, node->as.code.literal.len);
|
||||
cmark_strbuf_puts(xml, "</");
|
||||
cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
|
||||
literal = true;
|
||||
break;
|
||||
case CMARK_NODE_CUSTOM_BLOCK:
|
||||
case CMARK_NODE_CUSTOM_INLINE:
|
||||
cmark_strbuf_puts(xml, " on_enter=\"");
|
||||
escape_xml(xml, node->as.custom.on_enter.data,
|
||||
node->as.custom.on_enter.len);
|
||||
cmark_strbuf_putc(xml, '"');
|
||||
cmark_strbuf_puts(xml, " on_exit=\"");
|
||||
escape_xml(xml, node->as.custom.on_exit.data,
|
||||
node->as.custom.on_exit.len);
|
||||
cmark_strbuf_putc(xml, '"');
|
||||
break;
|
||||
case CMARK_NODE_LINK:
|
||||
case CMARK_NODE_IMAGE:
|
||||
cmark_strbuf_puts(xml, " destination=\"");
|
||||
escape_xml(xml, node->as.link.url.data, node->as.link.url.len);
|
||||
cmark_strbuf_putc(xml, '"');
|
||||
cmark_strbuf_puts(xml, " title=\"");
|
||||
escape_xml(xml, node->as.link.title.data, node->as.link.title.len);
|
||||
cmark_strbuf_putc(xml, '"');
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (node->first_child) {
|
||||
state->indent += 2;
|
||||
} else if (!literal) {
|
||||
cmark_strbuf_puts(xml, " /");
|
||||
}
|
||||
cmark_strbuf_puts(xml, ">\n");
|
||||
|
||||
} else if (node->first_child) {
|
||||
state->indent -= 2;
|
||||
indent(state);
|
||||
cmark_strbuf_puts(xml, "</");
|
||||
cmark_strbuf_puts(xml, cmark_node_get_type_string(node));
|
||||
cmark_strbuf_puts(xml, ">\n");
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
char *cmark_render_xml(cmark_node *root, int options) {
|
||||
char *result;
|
||||
cmark_strbuf xml = CMARK_BUF_INIT(cmark_node_mem(root));
|
||||
cmark_event_type ev_type;
|
||||
cmark_node *cur;
|
||||
struct render_state state = {&xml, 0};
|
||||
|
||||
cmark_iter *iter = cmark_iter_new(root);
|
||||
|
||||
cmark_strbuf_puts(state.xml, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
|
||||
cmark_strbuf_puts(state.xml,
|
||||
"<!DOCTYPE document SYSTEM \"CommonMark.dtd\">\n");
|
||||
while ((ev_type = cmark_iter_next(iter)) != CMARK_EVENT_DONE) {
|
||||
cur = cmark_iter_get_node(iter);
|
||||
S_render_node(cur, ev_type, &state, options);
|
||||
}
|
||||
result = (char *)cmark_strbuf_detach(&xml);
|
||||
|
||||
cmark_iter_free(iter);
|
||||
return result;
|
||||
}
|
|
@ -1 +1 @@
|
|||
Subproject commit 3478e691df49b9c0938220db57b03a9c6fcbec8d
|
||||
Subproject commit 23bf0e83aec1adaf81d9c50ce2385a98c20e3c93
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue