Phase 3A Help System
This commit is contained in:
parent
dd2917693a
commit
7ab5ccf374
@ -1,5 +0,0 @@
|
|||||||
https://dc.services.visualstudio.com/v2/track
|
|
||||||
Content-Type:application/x-json-stream
|
|
||||||
Content-Encoding:gzip
|
|
||||||
|
|
||||||
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYZDiupAhHFnqyr/fYSTqumM9mLvrGPe8/GcF16RV3tAPbo+HH5/Bp8mVKLklhAjTGAiMOFb2vVU9k1T1awhksl/UIncLbygHrVca6ol4BoEYG5Ig4faamwMSEqoIazhOafaTah/RcpVBp6dhipM3yBOLnjUo7+cN+F7rq1cpccwmyqGEdZ+Ssrr3MyX9eoWf77ebO83p1MTTFlcOYN6JJVVmmuDhw46zC0o3NXSYgOm1YzUumnqk8z5BNGrsZrM03t9E5KHpHtWsboimEl0LJFRSeWWBzXB9uWQu1gQ3eR4uYSXz/4VPUNEPSvfYJ7SXenRXe2nYXajuRrDbuf8bhW8dbs5qpQLl+gQwwFicrDA+c15FV8+h90u50Of1DgBKtEW4t55NZ5+3IBV85gewhwXMpN5+q8zqEdqTgGVaCE5jkvZ4jzManTg0y/ai8L3D8VmVMmGuP+wpIcT+/X/QH+n9KPzUKxvUI8IBWbqDrBlVGDeGYVVU0ssRC0sUC1bRlGJVsEn5+cwT8XaJ9idqX3o7zrqR5dApznmmf4W2WybGMysU/G+YEoqUtWEZNGcDnMqvoJxEXSCPMQ2zud8P0mYYIvokxvhFzyrOUbwqdio9Fj8oabHBUenqZJKKiW0ttAQsMQQJUhrQBgBhjPTGi65EVwSLbgaKDeqoYPNEVQiyCYrFl+LRrJaGoLtQAnmDenwoEWNOz5YKjtqO9V+gFvcj1mndEc4mBpLsAZzSRWWvK6xqRvVMmHbltkLTmdDUJKpX5rwzwnM2UtfTtZ+m93A8+ken/o0pgMjKGacNphLSXEnmxYPbTfIQXTQ6DzZKvgpvIH8BnEIk0v5Eblz3u3V+POR983cQvQwXmznzukYpmBTcbZl8WFfX2efX7Fiseh35/G/izFugn6CWGRjKecvlnk8Ho8/AAAA//8DAEJm1vgMBQAA
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
https://dc.services.visualstudio.com/v2/track
|
|
||||||
Content-Type:application/x-json-stream
|
|
||||||
Content-Encoding:gzip
|
|
||||||
|
|
||||||
H4sIAAAAAAAACpSUW2/cNhCF/4rAZ1Hm/aI3w27RhZt6ES/aoG8UZxQL0UoLito0MPa/F9RukLpveRMGOsPDb87wjUzhiKQl96fTL2ec8kJqkoetJJgwlBnK1IG7lvtW60Zo7a11f5OaDE/4jbTEqhh59EglGqQKmKad7CMFQM8ZBya0Kj3D54W0byQMDeB5iNjMy5+YlmGeSEv+GiaYv5azw9DEcV6hSfOIu2nJYYrFzB+7hyf6+/3+8Ly//rXgUsTNAKQlPvQhqgi0c+io6jFQJ31PAcFGwWTUWl5lw5QxTWFsFvjy43yY84Q5tqIRsmFUeHKpCYQciuUuLHj4diouNkSPpV5v5e2zfSNnTKQV9XeY13Z3cRzulrUb8YzjKaQF012cj8cwAanJKc0nTHnADcsZUzcvQy5EY7TBOc7BBau0BSe1kBYkQ3DMavDgg1GmF1HrHjrnreoVCK7AoFKdJTXZwI1jyMM8VTfvD+OAUyY1eX6p9mPI/ZyO79C/XInufgJosU1aIpmWxlpUUsrgTScYgjXRGNHFwGzfCeQRmfbKem4Zl14wL7WxzkJgPpKafAjxdZiw2j2SljCOAqRD2gtuqHIQaNDSU2Ok6ZFHbwUnNXmYpzxM67wu1W7K+Dlt9yUt+TWMC15vep/i65Ax5jUVCJ9MCeM+zbDGXP0IAGcNayRjRbTm05qrjwhDwpix4Dik9dbvfxJhxCZ6WFPCKVf7kF+r38LyujF0kQcffAgmxh41w54BC4ZZQAMGQQmwoLwCozyLRoWOKwiad32pkJpgyVu1RdwEpYzskArBLVUGDHVBO6qwQyV7Jhj07zhWz2PRheiYQpDUYw9UeR6oV1JSkDpYYXprRdF9R3ILC2cFMOD5uqxXBwAOwXAqFNdUec+p89rSzrrOd8ahjsXzE6YJx/+A+jDENC9zn6tb1qp36D6uU3lwqi13X4eJ/rPN6HGOXzBVZcZhmMp+3cZ6qckRw7ImPG7vVftG9mW5qsP2bBnTMO5lTV5ySHk93crWNd4xcblcLv8CAAD//wMAL93JHfQEAAA=
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
https://dc.services.visualstudio.com/v2/track
|
|
||||||
Content-Type:application/x-json-stream
|
|
||||||
Content-Encoding:gzip
|
|
||||||
|
|
||||||
H4sIAAAAAAAACpRUwW7cIBT8FYuzcQBjbHyLNo26Stus2lVb9YZ5zwmKg1eA00bR/nuFd6s0p6oXhJ6Zx7yZwS/Em0ckPbk8HN49oU+RlCS5tSSYUJQpyuSed71gPZOVZqxjrfpBSuJu8Jn0pJXWcquR1qiQSmANHerRUgDUnHFgopG5p7mLpH8hxlWAT85iNcevGKKbPenJN+dh/pnvNq6y07xAFeYJtz4m420m82m7uaEfLnf7293pVMSYwZUD0hNtRmOlBTp02FE5oqFdrUcKCK0VrLZNU59gzicM3kxVhIfX+2FOHpPtRSXqilGhybEkYJLJlAcTcf98yCxWia5yvVzL67Z/IU8YSC/KP2Ke2l3YyV08xmFxE1yc1mTiQ1zX0dg0h2dSkkOYDxiSw1WfyxjxcZie9yY+XJ/O5G189wvtkhA28+IT6QmvO1KSVaBpMsnNvjhz3EwOfVo/puB8dPZfvbI0t1+K3WTSOIfHN358Ocm8/Q+VPxp77zwW2yvSE8ZRQN0hHQVXVHZgqGlqTZWq1Yjc6lZwUpLN7JPzy7zEYusT3oV1JNKTazNFPPG7DPbeJbRpCXnO7yrnahdmWGwqXr3krGJVzVgGLemwpOIzggtoE+Yh9mE59/v6FiKUWEGbJQT0qdiZdF+8N/F+nbyz3GijjVHWjtgwHBkwo1gLqEAhSAEtSC1BSc2skmbgEkzDhzFXSEkwR6dY0yrVoKQdWqp1K6jsFKdDMwramLqRo5YDDtncVx2L2ynjjO2YRKipxhGo1NxQLeuaQt2YVqixbcX4lyTnPHCWBQZ8Or27lYEC6BAUp0LyhkqtOe1009Kh7QY9qA4bmznfYPA4/SXUR2fDHOcxFeeEFG+k+7z4/O8o1rT8dJ7+Wj26mu0DhiJ7bJzPT+Vs6/F4PP4GAAD//wMAZSWeyoIEAAA=
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
https://dc.services.visualstudio.com/v2/track
|
|
||||||
Content-Type:application/x-json-stream
|
|
||||||
Content-Encoding:gzip
|
|
||||||
|
|
||||||
H4sIAAAAAAAACoxU227cNhD9FYGvFRXeRJF6c20HXaSOjeyiLfo2Ioc2a5laUJSTwPC/F9LauaJAXwRhSM45c+bMPJEED0h6cnY8Xj5iKjOpSYlbSDChKdOUqQM3vWA9U41lWigj/yY1ie/wM+lJp5zjziKVqJEqz1o6yOCo92g5456JVq054XYm/ROB2Hh8jA6baf4D8xynRHryZ0x++rhiQ2zcOC2+ydOIuzQXSG4l8353/o7+fnZzuL453ZpxXh830ZOeWAjglPN0MGioCgjUSBuoR985waRrW3l6FlPBnGBsZn//Fd9PJWFxvWiEbBgVljzXxEOBlfIAMx4+H1cWm0QXa7zewttv/0QeMZNe1K9intK9cWN88zAPSxz9m+1LanLM0xFzibipcb2vvpLgrGGN0IIxUpMrcHcxYbW7qK7HtURwhin0kloMnirLgVolJfWyhU7o0HUikJq8w5xw/CbrVXR5mqdQqheRq+9w9mfnlwmGEVeMtzDOSGqym/cFkodxSnj5Cd1STrkOeVmPz6dUYlqmZa52qeBthpfz1/fnS86YSnUD5a76Dea7rUXGcbBgAbRzAVuGgXkGmnUetdfolfCdV1Z5rSxzWsHAlYeWD2GNkJr8umq4X5zDef7KZjPJOG4cqpc+nY8RU/lORtITxlF4aZAGwTVVxgOFVlqqtdQBubOd4K8wB8i3WEhPghmGgTk3CGW5A2FB+sCk18EK0XWggmw7JbRuWQfOd53gyssOHcggmRGkJjd58osrP7Zabg24ydM/6MoqFemJZm1rHAyD0IobsAYV18poi51VAZiSbgDeGhgGzqBlzhrhgGsLavBt+KLSZbqNCS/ifBzh8ze4ptGN/MUBGB5A/XD9bYYH/Djl+/cnGzfvLw+kJh+WtG6Earda5GNM9JNeJ/pqGUs83GUEj/5q8vizj3Adl2qbUOOw7bQJlAfDqWKar0bmVA+MgYUOguxITS4md4+5Wh0GMa1T9SXZ9b46y+4uFnRlySu/vzYe1/vqZoQSpvzw3Sb5prAfBWiE5GztjMfH0zbaOGrvDXrNqVC8pcpaTo1tOzp0ZrCDNti6VbHrpRyXUn1AHzO6slX84sUN8/wO3f3PYry64MWinK1e25+22Cbt/1pizzV5QJiXjA/btu6fyC4lzBvyxXIaxV26iuMYZ3RT8jPpbWtFwxjrXhj+572Oi4YJpZ+fn5//BQAA//8DAJsBfCEbBgAA
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
https://dc.services.visualstudio.com/v2/track
|
|
||||||
Content-Type:application/x-json-stream
|
|
||||||
Content-Encoding:gzip
|
|
||||||
|
|
||||||
H4sIAAAAAAAACpSTwW7cOAyGX8XQ2XIkWZYt34LJLjpIuxm0QbvYGy3SHaGOPJDltEUw776QZ4o2vfUiCLRJ/vx+6oUFeCLWs9vT6a9nCmlhJUt+CymhDBeGC/0ou16JXujKilao1v7HSubv6TvrWaudk84Sr8kQ1ygaPtSj44hkpZAoVKNzTfi8sP6Fga+Qnr2jal4+Ulz8HFjPPvmA89fcG3zlpnnFKs4T7cOSILgs5p/97p6/vT08Phwufy205OTKI+uZhRGcdsiHjjquRwLe1XbkSNg6JWrXNPUlzYdEMcBULfjlZ3+cU6DkelWpuhJcWXYuGUKCLHmAhR6/n7KKDdFdjpdbeLv2L+yZIutV+QPmpdyNm/yNm5+eIODN6INfjqxkpzifKCZPG45twmmC5OdQXJvsJk8hsZI9fCgOE6Rxjk+vGH24jL7/g8nfgTv6QMX+jvVMSFJYd8RHJQ3XHQKHprbcmNqMJJ1tlWQl280h+bDO61LsQ6LPcVPJevY3TAtd9N1Gd/SJXFpjlv6vyV7TN592M+aAYCU7xBlXl4qfuKWoRFWL/PFhTac1Fe8JfSSXKM/0GNdr+d9SlFFb0m6NkUIqDpCOxRtYjhuIzkmwYAGMcyM1gkaBAoxokQwaQq2wRW01Gm2FMxoGqREaOYw5knVnd4vLQllXDyCQNwQt12CAD2I72g7G1kLXuFdYi4cp54HrhCasuaURubYSuNV1zbFuoFVmbFs1/oLk6rgUmTfS8+VpbAoMYkdoJFdaNlxbK3lnm5YPbTfYwXTUuKz5nmKg6RdQ77yL8zKPqbguTPEK3fs15OddbMvz1Qf+bbPsbnZfKBbZcvAhb/PV5fP5fP4fAAD//wMAIOQppCUEAAA=
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
https://dc.services.visualstudio.com/v2/track
|
|
||||||
Content-Type:application/x-json-stream
|
|
||||||
Content-Encoding:gzip
|
|
||||||
|
|
||||||
H4sIAAAAAAAACpSTy27bMBBFf0Xg2lT4EiVqFzgtaqRtjMRoi+4ozqgmolAGReWBwP9eUHaRtLtuBGHAy7n3zPCVBPuApCWXh8OHRwxpIiuS/FISTGjKNGVqx5tWsJap0jApeMN+khXx1/hCWlIr57gzSCVqpApYRTvZOwqAhjMOTFQq32l/TaR9JdaXgI/eYTlO3zBOfgykJd99gPEp97a+dMM4QxnHATdhSja4bObrZn1NP19udzfb06kJpywuPZCWGNtbpxzQrsGGqh4tbaTpKSDUTjDpqkqeZD4kjMEO5QT3b/1hTAGTa0UpZMmoMOS4ImCTzZY7O+Hu5ZBdLIiucn21lJff9pU8YiStWP2Bebruwg3+4mHqZj/Axemb7HSfUx7ieMCYPC5MlpjDYJMfQ3HutB48hkRW5Oau2A429WN8+AvU3Sn/5j/i73L3D8/o5oSwHueQSEu44mRFvli39wGLzRVpCeMoQDZIe8E1VQ1YaitpqNZS98idqUXWrMeQfJjHeSo2IeGvuAQgLflohwlP1i+j2/uELs0xp/qh8y5s4wizS8Ubf85KVkrGsmhOhzkVtwg+okuY8+3ifL7vH4nQYhGt5xgxpGJr0774ZKf9AqVx3BprrNXO9Vgx7Bkwq1kNqEEjKAE1KKNAK8OcVrbjCmzFuz5XyIpgHnexbFgHoI2sKtqJilFVy54aXnVUKF2j1KbuMCveOBY3Q9ZZ1zCFIKnBHqgy3FKjpKQgK1sL3de16N8hOU+fswwY8PH0VhYHGqBB0JwKxSuqjOG0MVVNu7rpTKcbrFx2cI0x4PAO1Bfv4jiNfSrOy1P8he52Dvm9F8siPflAn5cZXY3uHmORZ2x9yOt9HuvxeDz+BgAA//8DAO0UJnI2BAAA
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpRTy27cOBD8FYFnUeZLlKibMd5FBk7Wg8RIFntrka0MEZkcUJSTwJh/X1AzgZPcciOKrO7qquYLCfCEZCC3p9NfzxjyQmqS/QYJJjRlmjL1yPuhbYdWNr3oW87Uf6Qm/h6/k4F0ylpuDVKJGqlyrKWjnCx1Dg1n3DHRqlITPi9keCHgG4fP3mITl4+YFh8DGcgnH1z8WnqDb+wcV9ekOOM+LBmCLWL+2e/u6dvbw+PD4fJqwaWQG+/IQEB0I5ecUQYgqVKAFEbRU4fYoYTWgp0uNB8ypgBzs7gvr/1dzAGzHUQjZMOoMORcEwcZiuQRFnz8fioqNovuCl5v8HYcXsgzJjKI+oeZl3I3dvY3Nj49QXA3kw9+OZKanFI8YcoeNzu2CecZso+hujbZzR5DJjV5+FAdZshTTE+/ePThMvr+DyZ/B/boA1b7OzIQxlE42SOdBNdU9Q4otNJQraWekFvTCU5qsosh+7DGdan2IePntKkkA/kb5gUv+m6TPfqMNq+pSP9Xl6zxm8+76ArASE0OKbrV5urVbs4a1khWLh/WfFpz9R6dT2gzlpke03ot/xtFaLGRdmtKGHJ1gHys3sByJAMx0FsOBgyAtnbCluHEHAPNOofaaXRKuM4po5xWhlmtYOTKQcvHqSBFd0m32hZK9Kzve3R0wnakCiyn4yglNa3WnFkt7VgYr7ZWD/MWh+2ZQiepwclRZThQo6SkTrbQCT11nShx/LDkmjhnxW+Hz5evsSnQzvXoNKdC8ZYqYzjtTdvRsetHM+oeW1sU3GMKOP9k1DtvU1zilKvrwlS/WPd+DeV7V9vyfPWBftsiu4v2C6aqRA4+lG2+pnw+n8//AwAA//8DADLvW0klBAAA
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpRTX2/cKBD/KhbPxgGMsfFbtOnpVrk2q3bVO93bmBk3KA5eYZy2iva7V3i3anNv94LQiJn5/eOVBXgm1rPb0+ndC4W0sJIlv5WUUIYLw4U+yq5vmr6pq07VStjmX1Yyf0/fWc9a7Zx0lnhNhrhG0fChHh1HJCuFRKEanWfCl4X1rwx8hfTiHVXz8pni4ufAeva3Dzh/zbvBV26aV6ziPNE+LAmCy2A+7Hf3/K/bw/HhcHm10JKbK4+sZ6DaQdZScAFQc62BOAyq40jUUg2NAzde2nxIFANM1YJPv/bjnAIl16tK1ZXgyrJzyRASZMgDLHT8fsooNonucr3cytu1f2UvFFmvyp9iXsbduMnfPC/D6ie8uZwJlqfM8hTnE8XkadNkozlNkPwciuum3eQpJFayh0/FYYI0zvH5jVCfLvz3/4P+MW9/943cmgh38xoS65nUgpXsPbhHH6jY37GeCUkK6474qKThukPg0NSWG1ObkaSzrZKsZLs5JB/WeV2KfUj0JW4EWM/+gGmhC/Tb6B59IpfWmFn9Y3IWDnHG1aXil/5SVKKqRUbysKbTmoqPhD6SS5T5HeN6nfefFmXU1rRbY6SQigOkx+JPWB5Zzyx0ToIFC2CcG6kRNAoUYESLZNAQaoUtaqvRaCuc0TBIjdDIYcwVVjLKdhdbwtQwdtqYlrfYGa5HsNwqqTi6zqpBdSMOwxsdi4dps8Z1QhPW3NKIXFsJ3Oq65lg30Coztq3K1vyU5Oq+FFlgpJfLX9kQGMSO0EiutGy4tlbyzjYtH9pusIPpqHEZ8z3FQNNvQr33Ls7LPKbiGp7ijXQf15D/e7EF6asP/Nvm0d3snigW2WPwIcf7auv5fD7/AAAA//8DACoRxvI2BAAA
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpSUT2/cNhDFv4rAsyjzP0XdDLtFDTfNIjbaorcRZxQL0UoLito0MPzdC2q3SNxbb8JAb/j4mzd8ZTMciXXs9nT66UxzXlnN8riXlFCOC8eFeZZtZ12nTKOCt176v1jNxkf6xjrmTYwyBuKaHHGDwvJeD5EjUpBColDWlJ7weWXdK4OxQTqPkZpl/Z3SOi4z69gf44zL13I2jE2clg2btEz0MK8Z5ljM/PZw98h/vT08fzxc/lppLeJmRNYxq0WQ3hNHNxhuBut4r6Xk0CuMSsoeKV5k45wpzTA1K375fj4ueaYcO9Uo3QiuAnurGUKGYrmHlZ6/nYqLHdF9qdd7ef/sXtmZEutU/S/MS7ubOI0369ZPdKbpBGmldBOX4xFmZDU7peVEKY+0YzlT6pd1zIVojB7aVkpswRvrsdVWaY9aELbCWwwYwBk3qGjtgH0bvBkMKmnQkTG9ZzXbwU0T5HGZq6v3u2mkObOafXyqDhPkYUnHd+ifLkQf/gfQYpt1TAurnfdktNYQXK8EoXfROdVHEH7oFclIwgbjg/RC6qBE0Nb51iOIUDp9gPgyzlQ93LOOCUkKdUt8UNJx0yJwsDpw57QbSMbglWQ1u1vmPM7bsq3Vw5zpc9rvyzr2M0wrXW56m+LLmCnmLRUIf7oSxkNacIu5+h4AKRrRaCGKaMunLVefCMdEMVPB8Zy2a7//SJRTu+huS4nmXB0gv1S/wPrCOhagjRICBAAX40BW0CBQgBMeyaEjNAo9mmDQmSCiM9BLg2BlP5QKqxmVvFV7xL2JAC1GbgYgbvoh8hYAOVmrowUCsvIdx+rjVHQQW2EINQ80IDdBAg9Ga47agldu8F4NPyC5hkWK0gzpfFnW3YFDbAmd5MpIy00IkrfBet77tg+9a8nG4vmR0kzTD6A+jDEt6zLk6pq16h26T9tcHpxqz93XceZ/7zO6X+IXSlWZMYxz2a/rWN9qdiRYt0TH/b3qXtmhLFf1vD9byjbBtzV7ypDydrpWrWikFO7t7e3tHwAAAP//AwBVXoe28wQAAA==
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpSUXW/jKBSG/4rFtXEBY2x81013tFGn02ianV3tHYbjFNWBCHBnqyr/fYSTqumM9mLvrGPe8/GcF16RU3tAPbo+HH5/BpciKlGyS4gRJjARmPAt7fpG9IxXNRNMEPoPKpG9hRfUo5ZrTbUEXIMAzA1p8FCPGhsDkhJqCGt4zql2EfWvSNnKwLPVUPn4DUK03qEe/WWd8d9zbWUrPfnZVMFPsHYxKadzM1/Wq1v8+Xqzvd+cTkWIWVxZg3rU1ETStgVsxMgxHxuBh5pSrAZmNKN0MKBPMusSBKemKpqn9/rGJwdJ96xidUUwk+hYIqOSyi0PKsL25ZC7WBDd5Hi5hJfP/hU9Q0A9K99gntJd6cle7eMw28lcTX63s2638m60uzmolAuX6BD8AUKysMD5zToVXj773S7nQ5/UFAGVaAthb52aTj9uYFTzlB78HBYy0Tz91xnUIzUnj0q0kJympWxxHmY1WXDpF+1F4fuHYjOpNPqw/7CkhxP79f9Af6f0o3VQrG9QjwgFZuoO8MiowLwzCqumlliIWoxAtWwZRSVaeZesm/0ci7VLsDtT+9DfddCPNoFOc8gz/S2y2TbBm1mn4n3BlFSkqgnJojkd5lR8BWMD6AR5iG2Yz/l+kjDBFtEnO8EveFZzCOBSsVHpsfhDxUfUI6k6TZVUUimh9QgNgZEYogRpDQgjwHBmWsMlN4JLogVXA+VGNXQYcwSVCLLJipOvB9UK2UksuGaYg+ywNC3FDdNEd60UnWQf4Bb3U9Yp3REOpsYSRoO5pApLXtfY1I1qmRjblo0XnM6GoCRTvzThnxHM2UtfTtZ+m93A8+keL30KYzowgmLGaYO5lBR3smnx0HaDHEQHjc6TrbyL/g3kNwiDjzblR+TOOrtX089H3jdzC8HBdLGdO6uDj35MxdmWxYd9fZ1dfsWKxaLfrcP/Lsa48foJQpGNpay7WObxeDz+AAAA//8DANXWaeQMBQAA
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpSUTW/cNhCG/4rAsyjzm6Juht2irpt6ES/SojeSM7KFaKUFRW0aGPvfA2o3SNxbb8JA78zwmXfmjUz+gKQjt8fjLyec8kJqkoctJJgwlBnK1J63nTadUI3QTlvO/yE1GR7xK+mIVTHy6JBKNEgVME2D7CMFQMcZBya0Kjn9y0K6N+KHBvA0RGzm5ROmZZgn0pG/hgnmL6W2H5o4zis0aR7xYVqyn2Jp5s+Hu0f6x+1u/7S7/LXgUsTNAKQjWjLHrUUKpldU9drQIDmnPgiIgvMAGC+yYcqYJj82C3z+UR/mPGGOnWiEbBgVjpxrAj770nLwC+6/HksXG6L7Eq+38PbZvZETJtKJ+jvMS7qbOA43eT6OeMLx6NOC6SbOh4OfgNTkmOYjpjzghmV76Tj6PMxTdS12Nw44ZVKTp+dqN/rcz+nwjtXzBcHD/yBwwhRIRyTT0liLSkrpnQmCIVgTjREhemb7IJBHZNop67hlXDrBnNTGthY8cyXTyzgHP/6+bPycYKEFZoURqNAJZYyLDGWLvbbIwBsTTBsdyNYK1UIrvfOeu4A2OhOLQT74+DpMWD3ck44wjgJki7QX3FDVgqdeS0eNkaZHHp0VnNTkbp7yMK3zulQPU8aXtPEjHfnVjwteyN2m+DpkjHlNBerfphTbpRnWmKsfDuCsYY1krIjWfFxz9RFhSBgzFrz7tF7z/UcijNhEd2tKOOVq5/Nr9ZtfXgsV30buXXmqibFHzbBnwLxhFtCAQVACLCinwCjHolE+cAVe89CXCKkJFsNVm8ed61sRQqCAmlEFGqlTXlEowEPrNLP4jmP1NBadjy1TCJI67IEqxz11SkoKUnsrTG+t6H9CcjUfZwUw4OmyrVsHBqBFMJwKxTVVznHaOm1psG1wwbSoY+n5EdOE40+gPgwxzcvc5+rq3eoduo/rVC5Otfn4yzDRf7cZ3c/xM6aqzNgPU1mw61jPNTmgX9aEh+1gdW9kV7ar2m93S+jG2bYmz9mnvB6vUc0azpk5n8/nbwAAAP//AwDcoTOW9AQAAA==
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACoxUTW/bOBD9KwLPpkJSFCUK2IPhtFgjTWMk7u5ibxQ5jInIokFRToMi/31BWa6dooe9CaP5eO/NG/5AvdoDatDycPh0hD4OaIGim0KMMIGJwIRvad2UomE8ryiXTBT/ogVyd/CGGlRxramWgAsQgLkhJW4Lq7ExICmhhrCSp57qeUDND6RcbuDoNOR++AvC4HyPGvS3641/TbOVy3XnR5MH38G6H6LqdQLzdb26w1+Wm+3D5pQ1wJCKc2dQg8qCSFpVgI2wHHNbCtwWlGLVMqMZpa0BfSpzfYTQqy4fzMtlvvGxh6gblrMiJ5hJ9L5ARkWVILdqgO3bIaGYJLpN8cUUnj6bH+gIATVscRbz1O5Gd+5mP7Sj68xNVOEZog1qD68+vMBRdWiBDsEfIEQHkzYPT9kFEiU5yZlghKAFuld653rI1rfZQ5cIK10TDqbAEqzBXFKFJS8KbIpSVUzYqmIWLdAdhB66q673Tgc/eBuzWfLsw5yV76PrRz8O2bqP8BxUPNV9Vt0AaIE2Y9u5YfcIyrxt/eOY/vVjl7isxhCgj9lGxV32pxp2qEFS1ZoqqaRSQmsLJQFLDFGCVAaEEWA4M5XhkhvBJdGCq5Zyo0ra2hRBC/QwxsMYz/p/vwKxDW6/B3NBsJ003nQqWh/2awN9dNal1ZwzJkN13cQqm3uuOgd9/CAyahChwExRA7aMCsxro7AqC4mFKIQFqmXF6Ekw657HnzoRaw0vbFnXopUtJcBsVXNS2ZYYbQstTdlCxSpS1VBLTUhbclq3UhMDtOQmsQvejDr+aoViWtDj2KfrzNaJ9qvr8XfBL4psgo9e+y4tzHXw9DZE2P9uayu/P/jBxcTfzqv9NsC979Pf+f5nzebipY+XGKQ7yKbTKzSHEmyNDeEM85ZUWLFS4oIayUvKKCsSwluvXyBkyWDK9dNOzp56eMqWQe9cBB3HkCb/M5E6rfPz+WQueuRfP21XPsDycFjM0T+OSaRTs7MBPrwr34aUvvNDvKI8U3uCzp6BXdnJwPH0VLkUFMbUYATFjNMScykprmVZ4baqW9mKGkp9sWv2CMYF0HHqtw1jmjYr+ztfLkN0Vuk4pOP54vVkp9mfc8qvIK/W9rP6NPxqdbOV5k6UJMs+nR7OyUH/6918f39//w8AAP//AwAnINo2KAYAAA==
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpRTTW/UMBD9K9Gc16ntOE6cW7UFsSrQFawAcXM8E2o1dVaOU0DV/nfk7KICNy6WNfKbeR/jZwj2kaCD6+Px1ROFNMMGkl9LkkvNuGZcHUTb1bqrZNnyujWq/gob8Lf0EzpolHPCGWIVaWIKec36anAMkYzgArmsVe5pv83QPYP1JdKTd1RO8yeKs58CdPDZB5y+59nWl26cFizjNNIuzMkGl8m8321v2dvr/eFuf34105zBpUfooK64EU1DDPWgmBpqzfpKCGZ7iU4K0SO5M8yHRDHYsZzx4WU+TilQcp0sZVVyJg2cNoA22Uy5tzMdfh4zi9Wim1zfrOX12j3DE0Xo5Oa3med2V270V49zv/gRr85nsvNDVnmM05Fi8rR6ssocR5v8FIrLpO3oKSTYwN3HYj/aNEzx8S+jPp717/5D/iFPf/WD3JIIt9MSEnQgVI7nnXX3PlCxu4EOuCCJVUtskEIz1aJltq4M07rSAwlnGilgA9spJB+WaZmLXUj0La4CoIPXdpzpTP06unufyKUlZlVfdB62jxMuLhUv/gte8rLiPIOWdFxS8YHQR3KJsr5DXC79/oFILVfQdomRQir2Nt0Xb+x8Dx0Y2zphjTXWaucGqjkNHLnVvEHSqAmVxAaVUaiV4U4r2wuFthb9kCuwAcpxF+uGKVJtK6VgfVNrpqqemKm4YYPG3sp+4L3JFr/4WNyNGWddyxVhxQwNyJQRlhlVVQyr2jZSD00jhz8suaQveDYY6en8V1YGGrEl1IJJJWqmjBGsNXXD+qbtTa9bql3mfEsx0PiHUe+8i9M8Dam4LE/xl3UflpD/e7Eu0ncf2I81o5vJPVAscsbWh7zel1hPp9PpFwAAAP//AwCS1lp7NgQAAA==
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACoxUy27kNhD8FYHXiDLfEnVzbC8ycPzAziAJcmuRLZuxTA0oyt6F4X8PpLHXj0WAXAShSXZVV1f3E4lwj6Qlx/v92QPGPJGS5LCGBBOGMkOZ2vGm1aaVomqYZUbYv0lJwjl+Jy2plXPcWaQSDVLlmaad7B31Hi1n3DOh1ZITbibSPhEIlceH4LAapz8wTWGMpCV/hujHxwUbQuWGcfZVGgfcxClDdAuZy83JOf39+Hp3dX24NeG0PK6CJy3Rklle10i96RVVvTa0k5xT6IR3gvPOozs8CzFjijBUk797w/djjphdKyohK0aFJc8l8ZBhodzBhLvv+4XFKtHpEi/X8PrbPpEHTKQV5auYh3RHbghH91M3h8EfrV9Skn0a95hywFWNq23xRoKzilXCCMZISS7A3YaIxea0uBqWEsE1TKGX1GLvqbIcqFVSUi811ML0dS16UpJzTBGHd1kvgkvjNPa5eBG5+ICzPT45i9ANuGB8gWFCUpLNtM0QPQxjxLNv6OZ8yLVL83J8MsYc4jzOU7GJGW8SvJy/vj+ZU8KYi2vIt8VvMN2SllhoHAcLFsA416Nm2DPPwLDao/EGvRK+9soqb5RlzijouPKgedcvEVKSXxcNt7NzOE1vbFaTDMPKoXjp08kQMOYPMpKWMI7CywZpL7ihqvFAQUtLjZGmR+5sLfgrzA7SDWbSkr7puo451wlluQNhQfqeSW96K0Rdg+qlrpUwRrManK9rwZWXNTqQvWSNICW5TqOfXf7cark24DqN/6DLi1SkJYZp3TjoOmEUb8A2qLhRjbFYW9UDU9J1wHUDXccZaOZsIxxwY0F1Xvc/VDqLNyHiaZj2A3x/h9tUppK/OICG96A+Xf+S4B4fx3R3ebBxdXm2IyX5OsdlIxSbxSKPIdJvZpnoi3nIYXebEDz6i9Hjzz7CZVyKdUJN33gNoKgA0VDVdYKC7y3VHGpkXiimJSnJ6ejuMBWLwyDEZap+JLvaFsfJ3YaMLs9p4ffXyuNqW1wPkPsx3X/YJO8K+yxAJSRnS2c8Phy20YGj9w16w6lQXFNlLaeN1TXt6qaznWlQu0Wxqznv51x8RR8SurxW/OLFFfPkFt3dz2K8uuDFopwtXtsettgq7f9aYs8luUeY5oT367Zun8gmRkwr8ul8GMVNvAjDECZ0Y/QTaRtleWW0ti8M//OeVrrSkqnn5+fnfwEAAP//AwBmsMk7GwYAAA==
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpRTy27cOBD8FYFnUeZLlKibMd5FBk7Wg8RIFnuj2K0MEZkcUJSTwJh/X1AzQeLcciOKrO7qquYLCfYJyUBuT6e/njHkhdQk+w0STGjKNGXqkfdDqwcpmp71PRfdf6Qm/h6/k4F0yjnuDFKJGqkC1tJRTo4CoOGMAxOtKjXt54UML8T6BvDZO2zi8hHT4mMgA/nkA8Svpbf1jZvjCk2KM+7Dkm1wRcw/+909fXt7eHw4XF4tuBRy44EMpJXM8K5DCnpSVE2tpqPknNpRgBOcj4DuQvMhYwp2bhb48rM/xBwwu0E0QjaMCkPONQGbbZE82gUfv5+Kis2iu4LXG7wdhxfyjIkMov5h5qXcjZv9jYtPTzbAzeSDX46kJqcUT5iyx82ObcJ5ttnHUF2b7GaPIZOaPHyoDrPNU0xPrzz6cBl9/weTv7Pu6ANW+zsyEMZRgOyRToJrqnqw1LbSUK2lnpA70wlOarKLIfuwxnWp9iHj57SpJAP5284LXvTdJnf0GV1eU5H+ry5Z4zefdxEKwEhNDinC6nL1027OGtZIVi4f1nxac/UewSd0GctMj2m9lv+NIrTYSLs1JQy5Oth8rN7Y5UgGYmzvuDXWWKudm7BlODFgVrMOUINGUAI6UEaBVoY5rezIFdiWj1NBiu6SbrUtFAM3QiuBQjtJqpQY6dhJSXFkTLhWc47jK1urh7nwrOuZQpDU4ARUGW6pUVJSkK3thJ66Tky/WHJNnLPiN+Dz5WtsCjRAj6A5FYq3VBnDaW/ajo5dP5pR99i6ovkeU8D5F6PeeZfiEqdcXRememXd+zWU711ty/PVB/pti+wuui+YqhK59aFs8zXl8/l8/h8AAP//AwB+LhBGJQQAAA==
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
https://dc.services.visualstudio.com/v2/track
|
||||||
|
Content-Type:application/x-json-stream
|
||||||
|
Content-Encoding:gzip
|
||||||
|
|
||||||
|
H4sIAAAAAAAACpRUTW/cNhT8KwLPoswvUaJuxjpBF27qRbJIi94ovqeYsEwtSMqJYex/L6jdIvGp6IUgnjiP82aGeiPBPiMZyO3p9OEFQ06kJtlvJcGEpkxTpo68H1o9SNH0TEoh+d+kJv4eX8lAOuUcdwapRI1UAWvpKCdHAdBwxoGJVpWe9lsiwxuxvgF88Q6bJX3FmPwSyED+9AGW7+Vu6xs3Lys0cZlxH1K2wRUyf+x39/T328Px4XA5lTAVcOOBDKSVzPCuQwp6UlRNraaj5JzaUYATnI+A7gLzIWMMdm4SPP28H5YcMLtBNEI2jApDzjUBm22hPNqEx9dTYbFJdFfq9VbetsMbecFIBlH/K+al3Y2b/c1zGlc/w81lzTY9pW2drMtLfCU1OcXlhDF73PS5TQmfx/n1aNPTx8uZsk0ffqBbM8JuWUMmA+GKk5psAs2zzX4J1ZXjbvYY8vYxRx+Sd//VS5KaPHypDrPN0xKf3/nx5SLz/n+o/Mm6Rx+w2t+RgTCOAmSPdBJcU9WDpbaVhmot9YTcmU6UQXZLyD6sy5qqfcj4LW4jkYF8tHPCC7/b6B59RpfXWOb8S5dcHeICq8vVTy85a1gjGSugNZ/WXH1G8BFdxjLEMa7Xfl/fQ4QWG2i3xoghVwebH6vfbHokAzG2d9waa6zVzk3YMpwYMKtZB6hBIygBHSijQCvDnFZ25Apsy8epVEhNsESn2tJqGHKcXEu1bjuqUI/UmqmjBtt2ZMh6dPBOx+phLjjreqYQJDU4AVWGW2qUlBRkazuhp64T0y+SXPPAWREY8OXy7jYGGqBH0JwKxVuqjOG0N21Hx64fzah7bF3hfI8x4PyLUJ+8i0taplxdE1K9k+7zGsq/o9rS8t0H+mPz6G5xTxir4rH1oTyVq63n8/n8DwAAAP//AwCYE5thggQAAA==
|
||||||
1
.appdata/buildverify/Help/Articles/Characters/.gitkeep
Normal file
1
.appdata/buildverify/Help/Articles/Characters/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
.appdata/buildverify/Help/Articles/StoryAssets/.gitkeep
Normal file
1
.appdata/buildverify/Help/Articles/StoryAssets/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
16
.appdata/buildverify/Help/Articles/Timeline/PlotLines.md
Normal file
16
.appdata/buildverify/Help/Articles/Timeline/PlotLines.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: Plot Lines
|
||||||
|
category: Timeline
|
||||||
|
tags: plot, threads, continuity
|
||||||
|
contextKey: timeline.plotLines
|
||||||
|
micro: Track unresolved story threads across scenes and books.
|
||||||
|
quickTitle: What are Plot Lines?
|
||||||
|
quickSummary: Plot Lines help you track unanswered questions, clues and story threads across chapters and books.
|
||||||
|
screenshot: plot-lines-overview.png
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
Plot Lines help you follow story threads across scenes, chapters, and books.
|
||||||
|
|
||||||
|
Use them to keep track of promises, clues, unresolved questions, payoffs, and continuity threads that need attention later in the story.
|
||||||
1
.appdata/buildverify/Help/Images/.gitkeep
Normal file
1
.appdata/buildverify/Help/Images/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
Binary file not shown.
23
PlotLine/Controllers/HelpDiagnosticsController.cs
Normal file
23
PlotLine/Controllers/HelpDiagnosticsController.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using PlotLine.Services;
|
||||||
|
using PlotLine.ViewModels;
|
||||||
|
|
||||||
|
namespace PlotLine.Controllers;
|
||||||
|
|
||||||
|
public sealed class HelpDiagnosticsController(IHelpService help) : Controller
|
||||||
|
{
|
||||||
|
public IActionResult Index()
|
||||||
|
{
|
||||||
|
return View(new HelpDiagnosticsViewModel
|
||||||
|
{
|
||||||
|
Articles = help.GetAllArticles(),
|
||||||
|
Coverage = help.GetCoverage()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult Article(string path)
|
||||||
|
{
|
||||||
|
var article = help.GetByRelativePath(path);
|
||||||
|
return article is null ? NotFound() : View(new HelpArticleDiagnosticViewModel { Article = article });
|
||||||
|
}
|
||||||
|
}
|
||||||
1
PlotLine/Help/Articles/Characters/.gitkeep
Normal file
1
PlotLine/Help/Articles/Characters/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
PlotLine/Help/Articles/GettingStarted/.gitkeep
Normal file
1
PlotLine/Help/Articles/GettingStarted/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
1
PlotLine/Help/Articles/StoryAssets/.gitkeep
Normal file
1
PlotLine/Help/Articles/StoryAssets/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
16
PlotLine/Help/Articles/Timeline/PlotLines.md
Normal file
16
PlotLine/Help/Articles/Timeline/PlotLines.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
---
|
||||||
|
title: Plot Lines
|
||||||
|
category: Timeline
|
||||||
|
tags: plot, threads, continuity
|
||||||
|
contextKey: timeline.plotLines
|
||||||
|
micro: Track unresolved story threads across scenes and books.
|
||||||
|
quickTitle: What are Plot Lines?
|
||||||
|
quickSummary: Plot Lines help you track unanswered questions, clues and story threads across chapters and books.
|
||||||
|
screenshot: plot-lines-overview.png
|
||||||
|
---
|
||||||
|
|
||||||
|
# Overview
|
||||||
|
|
||||||
|
Plot Lines help you follow story threads across scenes, chapters, and books.
|
||||||
|
|
||||||
|
Use them to keep track of promises, clues, unresolved questions, payoffs, and continuity threads that need attention later in the story.
|
||||||
1
PlotLine/Help/Images/.gitkeep
Normal file
1
PlotLine/Help/Images/.gitkeep
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
25
PlotLine/Models/HelpModels.cs
Normal file
25
PlotLine/Models/HelpModels.cs
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
namespace PlotLine.Models;
|
||||||
|
|
||||||
|
public sealed class HelpArticle
|
||||||
|
{
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string Category { get; set; } = string.Empty;
|
||||||
|
public IReadOnlyList<string> Tags { get; set; } = [];
|
||||||
|
public string ContextKey { get; set; } = string.Empty;
|
||||||
|
public string Micro { get; set; } = string.Empty;
|
||||||
|
public string QuickTitle { get; set; } = string.Empty;
|
||||||
|
public string QuickSummary { get; set; } = string.Empty;
|
||||||
|
public string Screenshot { get; set; } = string.Empty;
|
||||||
|
public string MarkdownContent { get; set; } = string.Empty;
|
||||||
|
public string RelativePath { get; set; } = string.Empty;
|
||||||
|
public IReadOnlyList<string> ValidationIssues { get; set; } = [];
|
||||||
|
public bool IsValid => ValidationIssues.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class HelpCoverageItem
|
||||||
|
{
|
||||||
|
public string ContextKey { get; set; } = string.Empty;
|
||||||
|
public string FilePath { get; set; } = string.Empty;
|
||||||
|
public bool IsValid { get; set; }
|
||||||
|
public IReadOnlyList<string> ValidationIssues { get; set; } = [];
|
||||||
|
}
|
||||||
@ -11,4 +11,11 @@
|
|||||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.1" />
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="7.0.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="Help\**\*">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@ -63,6 +63,7 @@ public class Program
|
|||||||
builder.Services.AddScoped<IArchiveService, ArchiveService>();
|
builder.Services.AddScoped<IArchiveService, ArchiveService>();
|
||||||
builder.Services.AddScoped<IAcceptanceService, AcceptanceService>();
|
builder.Services.AddScoped<IAcceptanceService, AcceptanceService>();
|
||||||
builder.Services.AddScoped<IImportService, ImportService>();
|
builder.Services.AddScoped<IImportService, ImportService>();
|
||||||
|
builder.Services.AddSingleton<IHelpService, HelpService>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
179
PlotLine/Services/HelpService.cs
Normal file
179
PlotLine/Services/HelpService.cs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using PlotLine.Models;
|
||||||
|
|
||||||
|
namespace PlotLine.Services;
|
||||||
|
|
||||||
|
public interface IHelpService
|
||||||
|
{
|
||||||
|
IReadOnlyList<HelpArticle> GetAllArticles();
|
||||||
|
HelpArticle? GetByContextKey(string contextKey);
|
||||||
|
HelpArticle? GetByRelativePath(string relativePath);
|
||||||
|
IReadOnlyList<HelpCoverageItem> GetCoverage();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class HelpService(IWebHostEnvironment environment) : IHelpService
|
||||||
|
{
|
||||||
|
private readonly object cacheLock = new();
|
||||||
|
private IReadOnlyList<HelpArticle>? cachedArticles;
|
||||||
|
|
||||||
|
public IReadOnlyList<HelpArticle> GetAllArticles()
|
||||||
|
{
|
||||||
|
if (cachedArticles is not null)
|
||||||
|
{
|
||||||
|
return cachedArticles;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (cacheLock)
|
||||||
|
{
|
||||||
|
cachedArticles ??= LoadArticles();
|
||||||
|
return cachedArticles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public HelpArticle? GetByContextKey(string contextKey)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contextKey))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetAllArticles().FirstOrDefault(x => string.Equals(x.ContextKey, contextKey, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public HelpArticle? GetByRelativePath(string relativePath)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(relativePath))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
var normalisedPath = relativePath.Replace('\\', '/');
|
||||||
|
return GetAllArticles().FirstOrDefault(x => string.Equals(x.RelativePath, normalisedPath, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<HelpCoverageItem> GetCoverage() =>
|
||||||
|
GetAllArticles()
|
||||||
|
.Select(x => new HelpCoverageItem
|
||||||
|
{
|
||||||
|
ContextKey = x.ContextKey,
|
||||||
|
FilePath = x.RelativePath,
|
||||||
|
IsValid = x.IsValid,
|
||||||
|
ValidationIssues = x.ValidationIssues
|
||||||
|
})
|
||||||
|
.OrderBy(x => x.ContextKey)
|
||||||
|
.ThenBy(x => x.FilePath)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
private IReadOnlyList<HelpArticle> LoadArticles()
|
||||||
|
{
|
||||||
|
var articleRoot = Path.Combine(environment.ContentRootPath, "Help", "Articles");
|
||||||
|
if (!Directory.Exists(articleRoot))
|
||||||
|
{
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return Directory
|
||||||
|
.EnumerateFiles(articleRoot, "*.md", SearchOption.AllDirectories)
|
||||||
|
.OrderBy(x => x, StringComparer.OrdinalIgnoreCase)
|
||||||
|
.Select(path => ParseArticle(articleRoot, path))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HelpArticle ParseArticle(string articleRoot, string filePath)
|
||||||
|
{
|
||||||
|
var rawContent = File.ReadAllText(filePath);
|
||||||
|
var relativePath = Path.GetRelativePath(articleRoot, filePath).Replace('\\', '/');
|
||||||
|
var metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var markdownContent = rawContent;
|
||||||
|
var issues = new List<string>();
|
||||||
|
|
||||||
|
if (rawContent.StartsWith("---", StringComparison.Ordinal))
|
||||||
|
{
|
||||||
|
using var reader = new StringReader(rawContent);
|
||||||
|
_ = reader.ReadLine();
|
||||||
|
var frontMatterLines = new List<string>();
|
||||||
|
string? line;
|
||||||
|
var frontMatterClosed = false;
|
||||||
|
|
||||||
|
while ((line = reader.ReadLine()) is not null)
|
||||||
|
{
|
||||||
|
if (line.Trim() == "---")
|
||||||
|
{
|
||||||
|
frontMatterClosed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
frontMatterLines.Add(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frontMatterClosed)
|
||||||
|
{
|
||||||
|
metadata = ParseFrontMatter(frontMatterLines);
|
||||||
|
markdownContent = reader.ReadToEnd().TrimStart();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
issues.Add("Front matter was opened but not closed.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
issues.Add("Front matter is missing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
var article = new HelpArticle
|
||||||
|
{
|
||||||
|
Title = GetValue(metadata, "title"),
|
||||||
|
Category = GetValue(metadata, "category"),
|
||||||
|
Tags = SplitTags(GetValue(metadata, "tags")),
|
||||||
|
ContextKey = GetValue(metadata, "contextKey"),
|
||||||
|
Micro = GetValue(metadata, "micro"),
|
||||||
|
QuickTitle = GetValue(metadata, "quickTitle"),
|
||||||
|
QuickSummary = GetValue(metadata, "quickSummary"),
|
||||||
|
Screenshot = GetValue(metadata, "screenshot"),
|
||||||
|
MarkdownContent = markdownContent,
|
||||||
|
RelativePath = relativePath
|
||||||
|
};
|
||||||
|
|
||||||
|
issues.AddRange(Validate(article));
|
||||||
|
article.ValidationIssues = issues;
|
||||||
|
return article;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<string, string> ParseFrontMatter(IEnumerable<string> lines)
|
||||||
|
{
|
||||||
|
var metadata = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
var separatorIndex = line.IndexOf(':');
|
||||||
|
if (separatorIndex <= 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = line[..separatorIndex].Trim();
|
||||||
|
var value = line[(separatorIndex + 1)..].Trim().Trim('"');
|
||||||
|
metadata[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return metadata;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> Validate(HelpArticle article)
|
||||||
|
{
|
||||||
|
var issues = new List<string>();
|
||||||
|
if (string.IsNullOrWhiteSpace(article.Title)) issues.Add("Title is required.");
|
||||||
|
if (string.IsNullOrWhiteSpace(article.ContextKey)) issues.Add("ContextKey is required.");
|
||||||
|
if (string.IsNullOrWhiteSpace(article.QuickTitle)) issues.Add("QuickTitle is required.");
|
||||||
|
if (string.IsNullOrWhiteSpace(article.QuickSummary)) issues.Add("QuickSummary is required.");
|
||||||
|
return issues;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetValue(IReadOnlyDictionary<string, string> metadata, string key) =>
|
||||||
|
metadata.TryGetValue(key, out var value) ? value : string.Empty;
|
||||||
|
|
||||||
|
private static IReadOnlyList<string> SplitTags(string tags) =>
|
||||||
|
string.IsNullOrWhiteSpace(tags)
|
||||||
|
? []
|
||||||
|
: tags.Split(',', StringSplitOptions.TrimEntries | StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
}
|
||||||
17
PlotLine/ViewModels/HelpViewModels.cs
Normal file
17
PlotLine/ViewModels/HelpViewModels.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
using PlotLine.Models;
|
||||||
|
|
||||||
|
namespace PlotLine.ViewModels;
|
||||||
|
|
||||||
|
public sealed class HelpDiagnosticsViewModel
|
||||||
|
{
|
||||||
|
public IReadOnlyList<HelpArticle> Articles { get; set; } = [];
|
||||||
|
public IReadOnlyList<HelpCoverageItem> Coverage { get; set; } = [];
|
||||||
|
public int TotalArticles => Articles.Count;
|
||||||
|
public int TotalValidArticles => Articles.Count(x => x.IsValid);
|
||||||
|
public int TotalInvalidArticles => Articles.Count(x => !x.IsValid);
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class HelpArticleDiagnosticViewModel
|
||||||
|
{
|
||||||
|
public HelpArticle Article { get; set; } = new();
|
||||||
|
}
|
||||||
52
PlotLine/Views/HelpDiagnostics/Article.cshtml
Normal file
52
PlotLine/Views/HelpDiagnostics/Article.cshtml
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
@model HelpArticleDiagnosticViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = $"Help Article - {Model.Article.ContextKey}";
|
||||||
|
}
|
||||||
|
|
||||||
|
<nav class="breadcrumb-trail" aria-label="Breadcrumb">
|
||||||
|
<a asp-action="Index">Help Diagnostics</a>
|
||||||
|
<span>@Model.Article.ContextKey</span>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Parsed help article</p>
|
||||||
|
<h1>@Model.Article.Title</h1>
|
||||||
|
<p class="lead-text">@Model.Article.RelativePath</p>
|
||||||
|
</div>
|
||||||
|
<a class="btn btn-outline-secondary" asp-action="Index">Back to diagnostics</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if (!Model.Article.IsValid)
|
||||||
|
{
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
<strong>Validation issues:</strong>
|
||||||
|
@string.Join("; ", Model.Article.ValidationIssues)
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
<section class="asset-detail-grid">
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Metadata</h2>
|
||||||
|
<dl class="row">
|
||||||
|
<dt class="col-sm-3">Context key</dt>
|
||||||
|
<dd class="col-sm-9">@Model.Article.ContextKey</dd>
|
||||||
|
<dt class="col-sm-3">Category</dt>
|
||||||
|
<dd class="col-sm-9">@Model.Article.Category</dd>
|
||||||
|
<dt class="col-sm-3">Tags</dt>
|
||||||
|
<dd class="col-sm-9">@string.Join(", ", Model.Article.Tags)</dd>
|
||||||
|
<dt class="col-sm-3">Micro</dt>
|
||||||
|
<dd class="col-sm-9">@Model.Article.Micro</dd>
|
||||||
|
<dt class="col-sm-3">Quick title</dt>
|
||||||
|
<dd class="col-sm-9">@Model.Article.QuickTitle</dd>
|
||||||
|
<dt class="col-sm-3">Quick summary</dt>
|
||||||
|
<dd class="col-sm-9">@Model.Article.QuickSummary</dd>
|
||||||
|
<dt class="col-sm-3">Screenshot</dt>
|
||||||
|
<dd class="col-sm-9">@Model.Article.Screenshot</dd>
|
||||||
|
</dl>
|
||||||
|
</article>
|
||||||
|
<article class="plain-card">
|
||||||
|
<h2>Markdown body</h2>
|
||||||
|
<pre class="mb-0 text-wrap">@Model.Article.MarkdownContent</pre>
|
||||||
|
</article>
|
||||||
|
</section>
|
||||||
80
PlotLine/Views/HelpDiagnostics/Index.cshtml
Normal file
80
PlotLine/Views/HelpDiagnostics/Index.cshtml
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
@model HelpDiagnosticsViewModel
|
||||||
|
@{
|
||||||
|
ViewData["Title"] = "Help Diagnostics";
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="page-heading">
|
||||||
|
<div>
|
||||||
|
<p class="eyebrow">Developer diagnostics</p>
|
||||||
|
<h1>Help Diagnostics</h1>
|
||||||
|
<p class="lead-text">Markdown article discovery and front matter validation.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="row g-3 mb-4">
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="plain-card">
|
||||||
|
<p class="eyebrow">Articles found</p>
|
||||||
|
<p class="writer-kpi">@Model.TotalArticles</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="plain-card">
|
||||||
|
<p class="eyebrow">Valid</p>
|
||||||
|
<p class="writer-kpi">@Model.TotalValidArticles</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-4">
|
||||||
|
<div class="plain-card">
|
||||||
|
<p class="eyebrow">Invalid</p>
|
||||||
|
<p class="writer-kpi">@Model.TotalInvalidArticles</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="list-section">
|
||||||
|
<h2>Coverage</h2>
|
||||||
|
@if (!Model.Articles.Any())
|
||||||
|
{
|
||||||
|
<p class="muted">No help articles were found.</p>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table align-middle">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Context key</th>
|
||||||
|
<th>Title</th>
|
||||||
|
<th>Category</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Validation issues</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach (var article in Model.Articles.OrderBy(x => x.ContextKey).ThenBy(x => x.RelativePath))
|
||||||
|
{
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a asp-action="Article" asp-route-path="@article.RelativePath">@article.ContextKey</a>
|
||||||
|
</td>
|
||||||
|
<td>@article.Title</td>
|
||||||
|
<td>@article.Category</td>
|
||||||
|
<td>
|
||||||
|
@if (article.IsValid)
|
||||||
|
{
|
||||||
|
<span class="badge text-bg-success">Valid</span>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<span class="badge text-bg-warning">Invalid</span>
|
||||||
|
}
|
||||||
|
</td>
|
||||||
|
<td>@string.Join("; ", article.ValidationIssues)</td>
|
||||||
|
</tr>
|
||||||
|
}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</section>
|
||||||
Loading…
x
Reference in New Issue
Block a user