Compare commits
350 Commits
Author | SHA1 | Date |
---|---|---|
Gary Ewan Park | 5ff6483ef7 | |
Travelmarx | f1fa3c9574 | |
Nick Vella | 8f7f4a7fb6 | |
Nick Vella | 743df72eed | |
Ravi Ranjan | 5324614c21 | |
Ravi Ranjan | 58b069cadd | |
Nick Vella | 75bdea0a97 | |
Ravi Ranjan | cbb541a7be | |
Nick Vella | 7f18b65c7a | |
Ravi Ranjan | 453d39cce8 | |
Ravi Ranjan | b4b9d70462 | |
Ravi Ranjan | 94c29fdcfe | |
Ravi Ranjan | dc49d8042b | |
Ravi Ranjan | 134ccf1bf9 | |
Nick Vella | eadf4601ba | |
Caio Silva | e628623e2a | |
Nick Vella | 09f1f05c76 | |
Nick Vella | f3c6a5ff69 | |
Nick Vella | e16f96ee22 | |
Nick Vella | dbbfd4f4ce | |
Nick Vella | b34cca1ff1 | |
Nick Vella | 857029dbee | |
Nick Vella | 462c13a0d6 | |
Nick Vella | 99458d7dbe | |
Nick Vella | b6c751399f | |
Nick Vella | c078072471 | |
Nick Vella | d22332a054 | |
Nick Vella | e543266caa | |
Nick Vella | c7bb28dbc9 | |
Nick Vella | 654465e576 | |
Nick Vella | c1591d8873 | |
Nick Vella | 5d7077e6c3 | |
Nick Vella | 9db6fdd6e9 | |
Nick Vella | bcac35626d | |
Nick Vella | b5fbf0367b | |
Nick Vella | 78c68a5367 | |
Nick Vella | 037d2682f1 | |
Nick Vella | 1fe0228189 | |
Nick Vella | 620b3e6ac0 | |
Nick Vella | fbe23bbaa5 | |
Nick Vella | 154a4f1495 | |
Nick Vella | de7b153364 | |
Nick Vella | bf1e8adb6f | |
Nick Vella | 232708a3f3 | |
Nick Vella | 7706b85796 | |
Nick Vella | 8ca2693615 | |
Nick Vella | 6c72d1d051 | |
Nick Vella | 3e0acdfb1a | |
Nick Vella | 662b9cf0d6 | |
Nick Vella | 91d3479839 | |
Nick Vella | dd95e6e4fb | |
Nick Vella | dab97c1e57 | |
Nick Vella | 025a49f1f5 | |
Nick Vella | 4451d96ba9 | |
Nick Vella | 88b018607b | |
Nick Vella | 06289e19d0 | |
Nick Vella | 6f524388c7 | |
Nick Vella | a64ae677a0 | |
Nick Vella | 27e979f19a | |
Nick Vella | 1fc8ac670f | |
Nick Vella | 753116f329 | |
Nick Vella | 1cc80f7b0d | |
Nick Vella | 406a63cc82 | |
Nick Vella | 3a1502dd4f | |
Nick Vella | 8d9f6f9204 | |
Nick Vella | 872961ee0a | |
Nick Vella | b1cbb5c6ad | |
Nick Vella | b7751ca027 | |
Nick Vella | e28f2e64c4 | |
Nick Vella | 8143dcf231 | |
Nick Vella | 318fd847f4 | |
Nick Vella | 0582307be9 | |
Nick Vella | e2fd2bffb9 | |
Nick Vella | 7622f29124 | |
Nick Vella | a6b7b81cb6 | |
Nick Vella | 6de3348b88 | |
Nick Vella | e8af31e41c | |
Nick Vella | 506e9a3638 | |
Nick Vella | 2f9749b8a8 | |
Nick Vella | 983c7b8389 | |
Nick Vella | 83c76f4a72 | |
Nick Vella | 46199f9ea6 | |
Nick Vella | 68d9c977d7 | |
Nick Vella | 0f386718e4 | |
Nick Vella | 01e7e63abe | |
Nick Vella | bd334dccf3 | |
Nick Vella | 4750b05edd | |
Nick Vella | 6e2db9bf42 | |
Nick Vella | bb6d3bfa1d | |
Nick Vella | 89fbbc1a3c | |
Nick Vella | 29d4ac314c | |
Nick Vella | c6952ab4e8 | |
Nick Vella | 4e35f56a55 | |
Nick Vella | b8def8bbb7 | |
Nick Vella | 9965452a5a | |
Nick Vella | 71c8ca6edb | |
Nick Vella | 08ac515ec2 | |
Nick Vella | f18011d75e | |
Nick Vella | cbaaa0f811 | |
Nick Vella | 8f098815d5 | |
Nick Vella | 6a67ca4579 | |
Nick Vella | 96606c3d0f | |
Nick Vella | 894a05e1a9 | |
Nick Vella | a314de778c | |
Nick Vella | 9ee13c5a59 | |
Nick Vella | 923acc40ad | |
Nick Vella | 1493078a2a | |
Nick Vella | 4ee27ca0c0 | |
Nick Vella | 45196f5448 | |
Nick Vella | 5ee2844533 | |
Nick Vella | cda9909d27 | |
Nick Vella | e34474d996 | |
Nick Vella | 844881a840 | |
Nick Vella | 7af5d3b9a5 | |
Nick Vella | 9a3e742945 | |
Nick Vella | 409e991101 | |
Nick Vella | 1812bf2761 | |
Nick Vella | 66e69f30bd | |
Nick Vella | aed86f9b3b | |
Nick Vella | f51cc368be | |
Nick Vella | a09c738141 | |
Nick Vella | 1c7c774795 | |
Nick Vella | ea9eaf32f9 | |
Nick Vella | 51a001841d | |
Nick Vella | b9287e65a4 | |
Nick Vella | b10e85e829 | |
Nick Vella | a00b06dd1b | |
Nick Vella | 4be51d4646 | |
Nick Vella | 85defa0b32 | |
Nick Vella | 0768c839b5 | |
Nick Vella | 3a71cea99f | |
Nick Vella | 48564482b5 | |
Nick Vella | 0c8290e4c8 | |
Nick Vella | 8359290301 | |
Nick Vella | 0e36c274e0 | |
Nick Vella | 103d47b376 | |
Nick Vella | 8dc9bbac90 | |
Nick Vella | 2dcbb3553e | |
Nick Vella | ba971fc2db | |
Nick Vella | 59808f70da | |
Nick Vella | 4623753bd8 | |
Nick Vella | 0d84ae9927 | |
Nick Vella | acc8eba6c2 | |
Nick Vella | d2a963ed1d | |
Nick Vella | 60ec2275b6 | |
Nick Vella | cc61210ad9 | |
Nick Vella | b81daeb4e0 | |
Nick Vella | 38c799388e | |
Nick Vella | dc19762c2e | |
Nick Vella | 76f7ec83b9 | |
Nick Vella | 21e987b52e | |
Nick Vella | 4ad1e89349 | |
Nick Vella | 9cd3fbf953 | |
Nick Vella | 689fee79e5 | |
Nick Vella | e614030d46 | |
Nick Vella | 48a5f68b08 | |
Nick Vella | 6a2c958be9 | |
Nick Vella | e3a3a28b84 | |
Nick Vella | 0d87b85402 | |
Nick Vella | 2844c175af | |
Nick Vella | d2a7522ff0 | |
Nick Vella | d30b2c038d | |
Nick Vella | 0ecb7ae9e1 | |
Nick Vella | 1108c5ee60 | |
Nick Vella | 645235bd54 | |
Nick Vella | 41f6ffabf2 | |
Nick Vella | 79b801708e | |
Nick Vella | d650f0d890 | |
Nick Vella | 362190c6f9 | |
Nick Vella | b411a942fa | |
Nick Vella | fc1503163a | |
Nick Vella | a3d48dbb27 | |
Nick Vella | 0ce5352ccb | |
Nick Vella | dd16678c00 | |
Nick Vella | 8eb1f321f2 | |
Nick Vella | 0b1ff58439 | |
Nick Vella | 71e601e03f | |
Nick Vella | 803312868f | |
Nick Vella | a895605b34 | |
Nick Vella | ce0fe5b080 | |
Nick Vella | 6711a6eeb6 | |
Nick Vella | 6c5341c6dc | |
Nick Vella | d4692079a3 | |
Nick Vella | a7b0b4c61d | |
Nick Vella | d55f47c65e | |
Nick Vella | b51341f2f8 | |
Jon Galloway | de565b29c6 | |
Nick Vella | 5a1bdb32b0 | |
Nick Vella | 505ffa4380 | |
Nick Vella | 547ae02d2d | |
Nick Vella | e04d675f11 | |
Nick Vella | f32565b60d | |
Nick Vella | 4cded49426 | |
Nick Vella | d015f16728 | |
Nick Vella | a364b74123 | |
Nick Vella | 21eadf04df | |
Nick Vella | 523be10b2e | |
Nick Vella | 42b158941d | |
Nick Vella | 1702425387 | |
Nick Vella | a36f4859a2 | |
Nick Vella | 25a29272df | |
Nick Vella | 6f7c5cb34c | |
Nick Vella | 80f6d25354 | |
Nick Vella | bd331f238f | |
Nick Vella | 11648544b8 | |
Nick Vella | 7790e21d4b | |
Nick Vella | 65819885ab | |
Nick Vella | a91bb7c27b | |
Nick Vella | 811f809c03 | |
Nick Vella | 3ce2e21d2d | |
Nick Vella | de3c157798 | |
Nick Vella | e91c110614 | |
Jon Galloway | afb7f965ff | |
Jon Galloway | 8bae8eb6f9 | |
Jon Galloway | 008ab86361 | |
Nick Vella | 63a0ff881d | |
Nick Vella | e0dc086348 | |
Nick Vella | cfc8143d6d | |
Nick Vella | 26a9b06229 | |
Nick Vella | 3f823e01a3 | |
Nick Vella | 67eae75aa7 | |
Nick Vella | 6bb325d36c | |
Nick Vella | 5c71216067 | |
Nick Vella | 9d5946fb0a | |
Nick Vella | 82eec4d55e | |
Nick Vella | 0f5a6c9755 | |
Nick Vella | dd063fc85f | |
Nick Vella | c95181bf21 | |
Nick Vella | 2cb98a94f1 | |
Nick Vella | 9c30545d38 | |
Nick Vella | 2965b2a65a | |
Nick Vella | 8ca41ac199 | |
Nick Vella | aadb13fc59 | |
Nick Vella | f1d16cc3e3 | |
Nick Vella | 4cf28cdb04 | |
Nick Vella | 22277ed2f9 | |
Nick Vella | b7b0c6b577 | |
Nick Vella | 40b92cd742 | |
Nick Vella | 1dc0c04452 | |
Nick Vella | b8003344d9 | |
Nick Vella | 72b197bdef | |
Nick Vella | 5976ae3b32 | |
Nick Vella | b32b6ef47a | |
Nick Vella | 7c061ba390 | |
Nick Vella | 299c6081d5 | |
Nick Vella | c7f36efabd | |
Nick Vella | 6a1c00e6c2 | |
Nick Vella | 0125c52198 | |
Nick Vella | b0a78d7085 | |
Nick Vella | 7bcaec39d8 | |
Nick Vella | d692a2862d | |
Nick Vella | 7cb8954bb0 | |
Nick Vella | 93c480dca0 | |
Nick Vella | 75bbefe40d | |
Nick Vella | 7323cc6e9e | |
Nick Vella | 24f08e63ea | |
Nick Vella | 7d02022ed1 | |
Nick Vella | b4b14cac51 | |
Nick Vella | 48cc98e125 | |
Nick Vella | ad39d20de7 | |
Nick Vella | 0c30378a40 | |
Nick Vella | aff3242dd3 | |
Nick Vella | e07613a00d | |
Nick Vella | e06c8234a8 | |
Nick Vella | 6119d37dd5 | |
Nick Vella | 21e72c5f4f | |
Nick Vella | 81ab755777 | |
Nick Vella | 9742077bc5 | |
Nick Vella | 36689de1ac | |
Nick Vella | ff4663ec9d | |
Nick Vella | 005cac2846 | |
Nick Vella | b6ac711cde | |
Nick Vella | db98134f8d | |
Nick Vella | 2365a9b949 | |
Nick Vella | 61275bcbdf | |
Nick Vella | d0b62a6b94 | |
Nick Vella | 96242f2f60 | |
Nick Vella | 651ff1575f | |
Nick Vella | 0f69b7f4b4 | |
Nick Vella | 571297d881 | |
Nick Vella | 8bd487fe13 | |
Nick Vella | ae72c81ac8 | |
Nick Vella | a51a71c491 | |
Nick Vella | ab6bc03a26 | |
Nick Vella | e1a622f46e | |
Nick Vella | 4fe0582645 | |
Nick Vella | 6b8d3d1fe8 | |
Nick Vella | 3eb2ad9513 | |
Nick Vella | e77d043585 | |
Nick Vella | 7fc61ab713 | |
Nick Vella | 150b924799 | |
Nick Vella | 6a75beed3d | |
Jon Galloway | aa5d6e2e39 | |
Jon Galloway | 556195f7f6 | |
Nick Vella | 74623609f7 | |
Nick Vella | f690377f22 | |
Nick Vella | 16a861e50a | |
Nick Vella | 9683414e61 | |
Nick Vella | 353560ddff | |
Nick Vella | 0051af6832 | |
Nick Vella | 46846b2119 | |
Nick Vella | 04e9610673 | |
Nick Vella | 86a9a0da3d | |
Nick Vella | 33cf68c1c7 | |
Nick Vella | d02e120386 | |
Nick Vella | a1c4d542e0 | |
Nick Vella | 27bc787537 | |
Jon Galloway | b327be3858 | |
Jon Galloway | 54b30e330b | |
Jon Galloway | 8ec3820a5c | |
Nick Vella | 55b440668a | |
Nick Vella | aaf92becd6 | |
Nick Vella | 5df49c466d | |
Nick Vella | 19a2b5c719 | |
Nick Vella | 5309ca2baf | |
Nick Vella | f5164e3464 | |
Nick Vella | 35e65f8a58 | |
Nick Vella | 8b83f29f72 | |
Nick Vella | 4f75609ede | |
Nick Vella | e1d6660119 | |
Nick Vella | fa95f4a9af | |
Nick Vella | 5fe336f957 | |
Nick Vella | bf382fe513 | |
Nick Vella | 2b3d69cc3c | |
Nick Vella | 39d9ba4c03 | |
Nick Vella | 455d385d04 | |
ppardi | b8cb10f2bf | |
Jon Galloway | 0612102be5 | |
Gary Ewan Park | b6ce41f47d | |
Jon Galloway | e158d0e2bb | |
Jon Galloway | cf8bd99017 | |
Jim Galasyn | f31228ede3 | |
Jim Galasyn | f2b73351af | |
Scott Lovegrove | 82b0be177a | |
plieblang | 6e2d8dc89a | |
Martin Brown | 4fd42158b1 | |
Jim Galasyn | f3c82fd767 | |
Jim Galasyn | 064d89b9f9 | |
plieblang | b83bb29845 | |
Josh Soref | 87c151e9b1 | |
manggsoft | 74a39a18a1 | |
manggsoft | cf5b42d420 | |
manggsoft | 7fbdd4b470 | |
manggsoft | ed787df5bd | |
Oren Novotny | ef5856adaa | |
Jon Galloway | 636feec6b9 | |
Jon Galloway | ffe06a3c93 | |
Jon Galloway | 9ee603db1b | |
Jon Galloway | 02abf2269d | |
Jon Galloway | c258b8572a |
|
@ -1,5 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2" xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3" xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
|
||||
<Package xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10"
|
||||
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10"
|
||||
xmlns:uap2="http://schemas.microsoft.com/appx/manifest/uap/windows10/2"
|
||||
xmlns:uap3="http://schemas.microsoft.com/appx/manifest/uap/windows10/3"
|
||||
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities"
|
||||
xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10"
|
||||
IgnorableNamespaces="uap uap2 uap3 mp rescap desktop">
|
||||
<Identity Name="D5BA6BCD.OpenLiveWriter" ProcessorArchitecture="x86" Publisher="CN=59FB1262-434E-4693-9A1E-CD275B151706" Version="0.6.1.0" />
|
||||
<Properties>
|
||||
<DisplayName>Open Live Writer</DisplayName>
|
||||
|
@ -24,7 +30,18 @@
|
|||
</uap:ShowNameOnTiles>
|
||||
</uap:DefaultTile>
|
||||
</uap:VisualElements>
|
||||
<Extensions />
|
||||
<Extensions>
|
||||
<uap3:Extension Category="windows.fileTypeAssociation">
|
||||
<uap3:FileTypeAssociation Name="openLiveWriter" Parameters=""%1"">
|
||||
<uap:SupportedFileTypes>
|
||||
<uap:FileType>.wpost</uap:FileType>
|
||||
</uap:SupportedFileTypes>
|
||||
<uap2:SupportedVerbs>
|
||||
<uap3:Verb Id="Edit" Parameters=""%1"">Edit</uap3:Verb>
|
||||
</uap2:SupportedVerbs>
|
||||
</uap3:FileTypeAssociation>
|
||||
</uap3:Extension>
|
||||
</Extensions>
|
||||
</Application>
|
||||
</Applications>
|
||||
</Package>
|
|
@ -22,5 +22,7 @@
|
|||
<file src="OpenLiveWriterSetup.exe" target="tools" />
|
||||
<file src="..\choco\chocolateyInstall.ps1" target="tools"/>
|
||||
<file src="..\choco\chocolateyUninstall.ps1" target="tools"/>
|
||||
</files>
|
||||
<file src="..\choco\VERIFICATION.TXT" />
|
||||
<file src="..\choco\LICENSE.TXT" />
|
||||
</files>
|
||||
</package>
|
|
@ -18,6 +18,7 @@
|
|||
<file src="Google.Apis.Auth.dll" target="lib\net451\Google.Apis.Auth.dll" />
|
||||
<file src="Google.Apis.Auth.PlatformServices.dll" target="lib\net451\Google.Apis.Auth.PlatformServices.dll" />
|
||||
<file src="Google.Apis.Blogger.v3.dll" target="lib\net451\Google.Apis.Blogger.v3.dll" />
|
||||
<file src="Google.Apis.Drive.v3.dll" target="lib\net451\Google.Apis.Drive.v3.dll" />
|
||||
<file src="Google.Apis.Core.dll" target="lib\net451\Google.Apis.Core.dll" />
|
||||
<file src="Google.Apis.PlatformServices.dll" target="lib\net451\Google.Apis.PlatformServices.dll" />
|
||||
<file src="html\map.html" target="lib\net451\html\map.html" />
|
||||
|
@ -61,6 +62,7 @@
|
|||
<file src="System.Net.Http.Primitives.dll" target="lib\net451\System.Net.Http.Primitives.dll" />
|
||||
<file src="template\default.htm" target="lib\net451\template\default.htm" />
|
||||
<file src="template\defaultstyle.css" target="lib\net451\template\defaultstyle.css" />
|
||||
<file src="YamlDotNet.dll" target="lib\net451\YamlDotNet.dll" />
|
||||
<file src="Zlib.Portable.dll" target="lib\net451\Zlib.Portable.dll" />
|
||||
</files>
|
||||
</package>
|
|
@ -1,12 +1,12 @@
|
|||
# Open Live Writer
|
||||
Open Live Writer makes it easy to write, preview, and post to your blog.
|
||||
For more information see http://www.OpenLiveWriter.org/.
|
||||
For more information see http://www.OpenLiveWriter.com/.
|
||||
|
||||
[![Build status](https://ci.appveyor.com/api/projects/status/8xpga2y53sgwo24g?svg=true)](https://ci.appveyor.com/project/dotnetfoundation/openlivewriter)
|
||||
|
||||
### Installation
|
||||
You can install the latest version of Open Live Writer alongside an [older version of Windows Live Writer](http://windows.microsoft.com/en-us/windows-live/essentials). Visit
|
||||
http://www.OpenLiveWriter.org to download and install the latest release.
|
||||
http://www.OpenLiveWriter.com to download and install the latest release.
|
||||
|
||||
### Latest News
|
||||
The current version of Open Live Writer is our first open source version.
|
||||
|
@ -15,7 +15,7 @@ look at the [roadmap](roadmap.md) to see what the current plans are.
|
|||
|
||||
For the latest news and updates about Open Live Writer, you can follow us on Twitter
|
||||
([@OpenLiveWriter](https://twitter.com/OpenLiveWriter)), by keeping an eye on the website
|
||||
http://www.OpenLiveWriter.org or by watching this repo and subscribing to notifications.
|
||||
http://www.OpenLiveWriter.com or by watching this repo and subscribing to notifications.
|
||||
|
||||
### Contributing
|
||||
Open Live Writer is an open source project and wouldn't exist without the passionate community of volunteer
|
||||
|
|
|
@ -10,14 +10,14 @@ if([string]::IsNullOrEmpty($env:SignClientSecret)){
|
|||
|
||||
$appSettings = "$currentDirectory\appsettings.json"
|
||||
|
||||
$appPath = "$currentDirectory\..\packages\SignClient\tools\SignClient.dll"
|
||||
$appPath = "$currentDirectory\..\packages\SignClient\tools\netcoreapp2.0\SignClient.dll"
|
||||
|
||||
$releases = ls $currentDirectory\..\Releases\*.exe | Select -ExpandProperty FullName
|
||||
|
||||
foreach ($release in $releases){
|
||||
Write-Host "Submitting $release for signing"
|
||||
|
||||
dotnet $appPath 'sign' -c $appSettings -i $release -s $env:SignClientSecret -n 'Open Live Writer' -d 'Open Live Writer' -u 'http://openlivewriter.org'
|
||||
dotnet $appPath 'sign' -c $appSettings -i $release -r $env:SignClientUser -s $env:SignClientSecret -n 'Open Live Writer' -d 'Open Live Writer' -u 'http://openlivewriter.org'
|
||||
|
||||
Write-Host "Finished signing $release"
|
||||
}
|
||||
|
|
|
@ -2,12 +2,12 @@
|
|||
"SignClient": {
|
||||
"AzureAd": {
|
||||
"AADInstance": "https://login.microsoftonline.com/",
|
||||
"ClientId": "1d2c0ac7-8c6d-4a4e-a568-67425aa48bd0",
|
||||
"TenantId": "dotnetfoundation.onmicrosoft.com"
|
||||
"ClientId": "c248d68a-ba6f-4aa9-8a68-71fe872063f8",
|
||||
"TenantId": "16076fdc-fcc1-4a15-b1ca-32c9a255900e"
|
||||
},
|
||||
"Service": {
|
||||
"Url": "https://dnfsignservice.azurewebsites.net/",
|
||||
"ResourceId": "http://sign.dotnetfoundation.org/server"
|
||||
"Url": "https://codesign.dotnetfoundation.org/",
|
||||
"ResourceId": "https://SignService/3c30251f-36f3-490b-a955-520addb85001"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
11
appveyor.yml
11
appveyor.yml
|
@ -4,21 +4,24 @@ pull_requests:
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
image: Visual Studio 2015
|
||||
image: Visual Studio 2017
|
||||
environment:
|
||||
OLW_CONFIG: Release
|
||||
OlwBloggerClientId: 597389294595-271ukaucs8ghmc6c6cnhrbef2c02g5qa.apps.googleusercontent.com
|
||||
OlwBloggerClientSecret:
|
||||
secure: ym7cbPINJz58iEgVlQCDbRz2W1CTYnxWsvMpMvxHFY4=
|
||||
SignClientSecret:
|
||||
secure: CHm+63iqVR0ddIHjGwE8nYF2EdUikH5VzCcszekUwptHkCLTuJ3C9ci8fcyox03s
|
||||
secure: Ks35w5ZOk78Bla9953hrhvDm2+t2NY83VgI+IcW92L0=
|
||||
SignClientUser:
|
||||
secure: v2LT5VnPWsq3/aLDN0xsyNjdwCZutUSH8CS/htCB8yXbzmwWrQIWs7TvJdrFqZB+
|
||||
nuget:
|
||||
disable_publish_on_pr: true
|
||||
build_script:
|
||||
- cmd: ./build.cmd
|
||||
- cmd: rmdir C:\Tools\vcpkg /q /s
|
||||
- cmd: ./build.cmd
|
||||
test: off
|
||||
install:
|
||||
- cmd: nuget install SignClient -Version 0.7.0 -SolutionDir %APPVEYOR_BUILD_FOLDER% -Verbosity quiet -ExcludeVersion
|
||||
- cmd: nuget install SignClient -Version 0.9.0 -SolutionDir %APPVEYOR_BUILD_FOLDER% -Verbosity quiet -ExcludeVersion
|
||||
after_build:
|
||||
- ps: '.\SignClient\Sign-Package.ps1'
|
||||
artifacts:
|
||||
|
|
46
build.cmd
46
build.cmd
|
@ -1,45 +1 @@
|
|||
@ECHO OFF
|
||||
|
||||
SETLOCAL
|
||||
|
||||
SET CACHED_NUGET=%LocalAppData%\NuGet\NuGet.exe
|
||||
SET SOLUTION_PATH="%~dp0src\managed\writer.sln"
|
||||
SET MSBUILD14_TOOLS_PATH="%ProgramFiles(x86)%\MSBuild\14.0\bin\MSBuild.exe"
|
||||
SET MSBUILD12_TOOLS_PATH="%ProgramFiles(x86)%\MSBuild\12.0\bin\MSBuild.exe"
|
||||
SET BUILD_TOOLS_PATH=%MSBUILD14_TOOLS_PATH%
|
||||
|
||||
IF NOT EXIST %MSBUILD14_TOOLS_PATH% (
|
||||
echo In order to run this tool you need either Visual Studio 2015 or
|
||||
echo Microsoft Build Tools 2015 tools installed.
|
||||
echo.
|
||||
echo Visit this page to download either:
|
||||
echo.
|
||||
echo http://www.visualstudio.com/en-us/downloads/visual-studio-2015-downloads-vs
|
||||
echo.
|
||||
echo Attempting to fall back to MSBuild 12 for building only
|
||||
echo.
|
||||
IF NOT EXIST %MSBUILD12_TOOLS_PATH% (
|
||||
echo Could not find MSBuild 12. Please install build tools ^(See above^)
|
||||
exit /b 1
|
||||
) else (
|
||||
set BUILD_TOOLS_PATH=%MSBUILD12_TOOLS_PATH%
|
||||
)
|
||||
)
|
||||
|
||||
IF EXIST %CACHED_NUGET% goto restore
|
||||
echo Downloading latest version of NuGet.exe...
|
||||
IF NOT EXIST "%LocalAppData%\NuGet" md "%LocalAppData%\NuGet"
|
||||
@powershell -NoProfile -ExecutionPolicy unrestricted -Command "$ProgressPreference = 'SilentlyContinue'; Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile '%CACHED_NUGET%'"
|
||||
|
||||
:restore
|
||||
IF EXIST "%~dp0src\packages" goto build
|
||||
"%CACHED_NUGET%" restore %SOLUTION_PATH%
|
||||
|
||||
:build
|
||||
|
||||
IF "%OLW_CONFIG%" == "" (
|
||||
echo %%OLW_CONFIG%% not set, will default to 'Debug'
|
||||
set OLW_CONFIG=Debug
|
||||
)
|
||||
|
||||
%BUILD_TOOLS_PATH% %SOLUTION_PATH% /nologo /maxcpucount /verbosity:minimal /p:Configuration=%OLW_CONFIG% %*
|
||||
powershell .\build.ps1
|
|
@ -0,0 +1,109 @@
|
|||
# Cause powershell to fail on errors rather than keep going
|
||||
$ErrorActionPreference = "Stop";
|
||||
|
||||
@"
|
||||
|
||||
=======================================================
|
||||
Checking solution exists
|
||||
=======================================================
|
||||
"@
|
||||
|
||||
$solutionFile = "$PSSCRIPTROOT\src\managed\writer.sln"
|
||||
if (-Not (Test-Path "$solutionFile" -PathType Leaf))
|
||||
{
|
||||
"Unable to find solution file at $solutionFile"
|
||||
exit 100
|
||||
}
|
||||
"Solution found at '$solutionFile'"
|
||||
|
||||
@"
|
||||
|
||||
=======================================================
|
||||
Fetching MSBuild location
|
||||
=======================================================
|
||||
"@
|
||||
|
||||
# Install module to allow us to find MSBuild
|
||||
# See https://github.com/Microsoft/vssetup.powershell
|
||||
Install-Module VSSetup -Scope CurrentUser
|
||||
|
||||
$visualStudioLocation = (Get-VSSetupInstance `
|
||||
| Select-VSSetupInstance -Version '[15.0,16.0)' -Latest).InstallationPath
|
||||
|
||||
$msBuildExe = $visualStudioLocation + "\MSBuild\15.0\Bin\msbuild.exe"
|
||||
IF (-Not (Test-Path -LiteralPath "$msBuildExe" -PathType Leaf))
|
||||
{
|
||||
"MSBuild not found at '$msBuildExe'"
|
||||
"In order to build OpenLiveWriter either Visual Studio 2017 (any edition) or Build "
|
||||
"Tools for Visual Studio 2017 must be installed."
|
||||
"These can be downloadd from https://visualstudio.microsoft.com/downloads/"
|
||||
exit 101
|
||||
}
|
||||
|
||||
"MSBuild.exe found at: '$msBuildExe'"
|
||||
|
||||
@"
|
||||
|
||||
=======================================================
|
||||
Ensureing nuget.exe exists
|
||||
=======================================================
|
||||
"@
|
||||
|
||||
$nugetPath = "$env:LocalAppData\NuGet"
|
||||
$nugetExe = "$nugetPath\NuGet.exe"
|
||||
if (-Not (Test-Path -LiteralPath "$nugetExe" -PathType Leaf))
|
||||
{
|
||||
if (-Not (Test-Path -LiteralPath "$nugetPath" -PathType Container))
|
||||
{
|
||||
"Creating Directory '$nugetPath'"
|
||||
New-Item "$nugetPath" -Type Directory
|
||||
}
|
||||
"Downloading nuget.exe"
|
||||
Invoke-WebRequest 'https://dist.nuget.org/win-x86-commandline/latest/nuget.exe' -OutFile "$nugetExe"
|
||||
}
|
||||
|
||||
"Nuget.exe found at: '$nugetExe'"
|
||||
|
||||
@"
|
||||
|
||||
=======================================================
|
||||
Ensure nuget packages exist
|
||||
=======================================================
|
||||
"@
|
||||
|
||||
$packageFolder = "$PSSCRIPTROOT\src\managed\packages"
|
||||
if (Test-Path -LiteralPath $packageFolder)
|
||||
{
|
||||
"Packages found at '$packageFolder'"
|
||||
}
|
||||
else
|
||||
{
|
||||
"Running nuget restore"
|
||||
& $nugetExe restore $solutionFile
|
||||
}
|
||||
|
||||
@"
|
||||
|
||||
=======================================================
|
||||
Check build type
|
||||
=======================================================
|
||||
"@
|
||||
|
||||
if (-Not (Test-Path env:OLW_CONFIG))
|
||||
{
|
||||
"Environment variable OWL_CONFIG not set, setting to 'Debug'"
|
||||
$env:OLW_CONFIG = 'Debug'
|
||||
}
|
||||
|
||||
"Using build '$env:OLW_CONFIG'"
|
||||
|
||||
@"
|
||||
|
||||
=======================================================
|
||||
Starting build
|
||||
=======================================================
|
||||
"@
|
||||
Get-Date
|
||||
$buildCommand = "`"$msBuildExe`" $solutionFile /nologo /maxcpucount /verbosity:minimal /p:Configuration=$env:OLW_CONFIG $ARGS"
|
||||
"Running build command '$buildCommand'"
|
||||
Invoke-Expression "& $buildCommand"
|
|
@ -0,0 +1,2 @@
|
|||
@echo Building Strings resource and StringId enum from Strings.csv
|
||||
src\managed\bin\Debug\i386\Writer\locutil.exe /s:src\managed\OpenLiveWriter.Localization\Strings.csv /senum:src\managed\OpenLiveWriter.Localization\StringId.cs /strings:src\managed\OpenLiveWriter.Localization\Strings.resx /props:src\managed\OpenLiveWriter.Localization\Properties.resx /propsnonloc:src\managed\OpenLiveWriter.Localization\PropertiesNonLoc.resx
|
|
@ -0,0 +1,23 @@
|
|||
Open Live Writer
|
||||
|
||||
Copyright (c) .NET Foundation. All rights reserved.
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
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.
|
|
@ -0,0 +1,10 @@
|
|||
VERIFICATION
|
||||
Verification is intended to assist the Chocolatey moderators and community in verifying that this package's contents are trustworthy.
|
||||
|
||||
This package is owned and updated by the members of the OpenLiveWriter Organisation on GitHub:
|
||||
|
||||
https://github.com/OpenLiveWriter
|
||||
|
||||
If you have any concerns about the contents of this package, please raise an issue here:
|
||||
|
||||
https://github.com/OpenLiveWriter/OpenLiveWriter/issues
|
|
@ -26,6 +26,15 @@ If you do, then you will need to do the following in order to use Live Writer wi
|
|||
8. If you haven't added any details for your project's consent screen, you'll need to do so at this point.
|
||||
9. When prompted for your application type, choose other, and just give it a name, then click Create
|
||||
10. You will then be shown the client ID and Client Secret that need to be used.
|
||||
11. In your Live Writer repository, go to writer.build.targets and open it in a text editor.
|
||||
11. In your Live Writer repository, go to `\src\managed\OpenLiveWriter.BlogClient\Clients\GoogleBloggerv3Secrets.json` and open it in a text editor.
|
||||
> Note: If this file does not exist, create it and add the following:
|
||||
```json
|
||||
{
|
||||
"installed": {
|
||||
"client_id": "PASTE_YOUR_CLIENT_ID_HERE",
|
||||
"client_secret": "PASTE_YOUR_CLIENT_SECRET_HERE"
|
||||
}
|
||||
}
|
||||
```
|
||||
12. Replace the ClientId and Client Secret details where it says PASTE\_YOUR\_CLIENT\_ID\_HERE and PASTE\_YOUR\_CLIENT\_SECRET\_HERE respectively
|
||||
13. Now you should be able to build Live Writer again and connect your Blogger blog.
|
||||
13. Now you should be able to build Live Writer again and connect your Blogger blog.
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
# Hacking - Static Site
|
||||
|
||||
The Static Site Generator support is split into various classes, as indicated by the class diagram below.
|
||||
|
||||
![](images/StaticSiteClassDiagram.png)
|
||||
|
||||
Please note that this diagram only describes classes created as part of the Static Site Generator implementation, and only classes in the `OpenLiveWriter.BlogClient.Clients.StaticSite` namespace. Other classes are implemented, such as wizard pages, which have been ommited from the above diagram.
|
||||
|
||||
## Class roles and descriptions
|
||||
|
||||
|Name|Role and description|Dependency summary|
|
||||
|---|---|---|
|
||||
|StaticSiteClient|Serves as the primary interface between Open Live Writer and the Static Site Generator support. Implements Blog functions such as CRUD on posts and pages.|<ul><li>Implements IBlogClient</li><li>Inherits BlogClientBase</li><li>Creates a private `StaticSiteConfig` and loads its contents from the provided `IBlogClientCredentialsAccessor` on initiation.</li></ul>|
|
||||
|StaticSiteConfig|Defines and handles the storage and processing of configuration settings for the Static Site Generator support.|<ul><li>Instantiated in `StaticSiteClient`</li><li>Used-by-reference in `StaticSiteConfigDetector`.</li><li>Instantiates a `StaticSiteConfigFrontMatterKeys`, to be loaded from stored config values.</li></ul>|
|
||||
|StaticSiteItem|Abstract class which represents a published or yet-to-be-published generic item to `StaticSiteClient`. Contains methods related to loading and saving, as well as generic methods which are based upon through sub-classes.|<ul><li>Contains a `BlogPost` (from `OpenLiveWriter.Extensibility.BlogClient`)</li><li>Generates a `StaticSiteItemFrontMatter` on attribute request</li><li>Abstract class, never instantiated.</li></ul>|
|
||||
|StaticSitePost|Sub-class of `StaticSiteItem` representing a Post item.|Sub-classes `StaticSiteItem`|
|
||||
|StaticSitePage|Sub-class of `StaticSiteItem` representing a Page item.|Sub-classes `StaticSiteItem`|
|
||||
|StaticSiteItemFrontMatter|Defines and stores all possible static item front matter keys. Implements loading and saving from YAML, as well as loading and saving from a `OpenLiveWriter.Extensibility.BlogClient.BlogPost` instance.|<ul><li>Instantiated by `StaticSiteItem` on request.</li><li>Retrieves and stores a `StaticSiteConfigFrontMatterKeys` from `StaticSiteConfig` on construction.</li></li></ul>|
|
||||
|StaticSiteConfigFrontMatterKeys|A subset of the static site config, `StaticSiteConfigFrontMatterKeys` contains the key names for each of the supported front-matter attributes. Used to support different static site generators.|<ul><li>Instantiated by StaticSiteConfig.</li><li>Used-by-reference in StaticSiteItemFrontMatter</li></ul>|
|
|
@ -0,0 +1 @@
|
|||
<mxfile modified="2019-07-19T10:12:49.903Z" host="www.draw.io" agent="Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3851.0 Safari/537.36 Edg/77.0.222.0" etag="koplk2OEt1fMux2tFgc2" version="11.0.0" type="device"><diagram id="x-93gLlAT9IVamWt818S" name="Page-1">7Vpbb+MoFP41eZzI4Gsep2m7O7vtbqWMNNK+UZvY7GCIMLnNr1+IcXxNmk6SBq1Gqhp8OGA4H9/HsfHIneab3wRaZM88wXQEnWQzcu9HEAIPwpH+c5JtaQk9UBpSQRLjVBtm5Ac2RsdYlyTBRctRck4lWbSNMWcMx7JlQ0Lwddttzmn7rguU4p5hFiPat34jicxKa+Q7tf13TNKsujNwTE2OKmdjKDKU8HXD5D6M3KngXJalfDPFVAevikvZ7vFA7X5gAjN5SgMJePxHyhKwnvpFuOKbe+x/AlHZzQrRpZmxGa3cViFIBV8u+nczA1hhIfFmCAv0WvVQT1etE8xzLMVW+a3rgIbAL9tmjWB6VSyRATHdt63nqQpmqu+Ztvv2tBVeC13USwaLkXs350zOTD1Q18UCxYSlX/lCGxxlUVOuFokLg9KwXx06BNrwwgsiCWfKRPFc1dxlMqemz36Yj8PWDf6NgvwM3b9+PBVkOkv/mWfLVf7l3vvk9UKKE8Utc8mFzHjKGaIPtfVOLTWWYN2rjmft88TLKCvjv1jKrREKtJS8HUDMks+a9uoyISjnLPmaEVZWPBLacDNdQGcfdj28g+Q6ikPBlyLGR2Jh1ptEIsVHOxwGUWCKJFm1RzcE2K6pCgDaNhwWnDBZNHp+0QblYFYODIOyR6PRIOwoSccfROCYvyqUI6iXzn4qZ1B2gLEB1fQhqpDqQkxRoabpVBXqPvu6mVQBjGdE4iklGtTu2mysoXWm3GaK3bpqrfa1qxDT1E4m40kIgkkEgAud0Pdboa32mQaLQQDGURSEE+j5MHId2Oe0fy3dhBcDgbM5Sa0BwXXh2A9gNHGdCIQBsBmD0FJZdQ7J6k+p6LHld5qK9vT8DFk9C7ChXOMM0txjqZIrLqwhj+85Y6D+RZHjOy7wrFawqBe2X+wZXq+WsGdiKWAHs8jLJY03k7sDaR9s74uw++RQTsi0qmF/bzo6OZpd2pGNem9ruvpFuRZe2r9Cr4UUKJZ7a7tFdbXzfRXv2yW+SJxbszccz25daNfmUL10+p+LzaCjf6LYeFbtDv6lcivNmkfBmXxGUmKLkivXGTuHnkys4w+wlD8fkF0FJ/IHWsUf0H/v+QuxYY2xBLHgbcVTucVS5xYnPU42RO9PvC3sEb5K26q0LvD7WhcNqFt4NXU7IfQM5bjYRWg4+n8vMHtSa+SbUKEU4zvK0/J95Lj8KcY1Pj0oVOQ6xwUKaf5dAUm5UBbGmabnXDGoY0KUpPrAIVa32J1laBxIjOhnU5GTJNlxewjjNt8vj3gD0f2O1kQUXuuEwr+x+L1SHn8flL4S2OowqZ3+AfcjTixOffj0PkgMjw3y/PTvhRc/eSQwcP55IgiHH5o6z0ywJYSwSuk+PukbxCCwlkQdyvQ5dXUSnXrsd1MSXez99Iv+lsIWEoGJP/aaNPJuxCJ1WX/kUb5/qj+VcR/+Aw==</diagram></mxfile>
|
Binary file not shown.
After Width: | Height: | Size: 25 KiB |
|
@ -26,7 +26,7 @@
|
|||
</feature>
|
||||
<feature name="Allow Multiselect Images" enabled="true" />
|
||||
<feature name="Blog Providers" enabled="true">
|
||||
<parameter name="supported" value="16B3FA3F-DAD7-4c93-A407-81CAE076883E;4AA58E69-8C24-40b1-BACE-3BB14237E8F9;556A165F-DA11-463c-BB4A-C77CC9047F22;B6F817C3-9D39-45c1-A634-EAC792B8A635;D48F1B5A-06E6-4f0f-BD76-74F34F520792;3C53BD64-4FA8-4bc7-8CB9-5D3BB58FAD07;B9C51DE3-F2DE-4204-AE7D-F64116429401;E0500CDC-91C9-4822-855F-CDC6F49B2735;CD671F17-1902-46e5-8266-A24D6C602234;82E6C828-8764-4af1-B289-647FC84E7093;CAA1EBAA-946E-4143-9733-F01CC8DBE827;D3405AD6-9E02-43b3-87F4-08F5A5946B40;E83A8A63-0F40-4499-AAD1-DFB2577CCC40;A82F57E0-41D3-45e9-861E-8C527844BCA6;494B3F2D-206D-449a-B0FA-A987E57C95D1;BAF4FE16-25FB-4a94-90C7-11A1B30CAD61;7D215B2D-7B57-432a-B382-ED027321C480;4FCDB0E2-66F8-4b4d-828B-B2EED6147789;785D4CDA-602F-481b-ACEA-3B3DB79B0F43;1AAFB2D8-F974-413A-9DBB-9C826CCDA7BC" />
|
||||
<parameter name="supported" value="16B3FA3F-DAD7-4c93-A407-81CAE076883E;4AA58E69-8C24-40b1-BACE-3BB14237E8F9;556A165F-DA11-463c-BB4A-C77CC9047F22;B6F817C3-9D39-45c1-A634-EAC792B8A635;D48F1B5A-06E6-4f0f-BD76-74F34F520792;3C53BD64-4FA8-4bc7-8CB9-5D3BB58FAD07;B9C51DE3-F2DE-4204-AE7D-F64116429401;E0500CDC-91C9-4822-855F-CDC6F49B2735;CD671F17-1902-46e5-8266-A24D6C602234;82E6C828-8764-4af1-B289-647FC84E7093;CAA1EBAA-946E-4143-9733-F01CC8DBE827;D3405AD6-9E02-43b3-87F4-08F5A5946B40;E83A8A63-0F40-4499-AAD1-DFB2577CCC40;A82F57E0-41D3-45e9-861E-8C527844BCA6;494B3F2D-206D-449a-B0FA-A987E57C95D1;BAF4FE16-25FB-4a94-90C7-11A1B30CAD61;7D215B2D-7B57-432a-B382-ED027321C480;4FCDB0E2-66F8-4b4d-828B-B2EED6147789;785D4CDA-602F-481b-ACEA-3B3DB79B0F43;1AAFB2D8-F974-413A-9DBB-9C826CCDA7BC;9E0EC9F9-AFDD-4812-9298-8963D5DB94CC" />
|
||||
</feature>
|
||||
<feature name="Tag Providers" enabled="true">
|
||||
<parameter name="supported" value="63DA9645-F182-4da7-88BC-B430ECA1AD75;711ab367-b19d-45e7-979f-6a0da37d59e3;0AF393BB-B94E-4ea8-8A6D-315C378819AB;A3F2B48B-48D4-4067-8A5F-DD04B49C3052" />
|
||||
|
@ -78,4 +78,4 @@
|
|||
<parameter name="url" value="http://openlivewriter.org/WriterRedirect/CreateSpaceAddress" />
|
||||
</feature>
|
||||
</market>
|
||||
</features>
|
||||
</features>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</feature>
|
||||
<feature name="Allow Multiselect Images" enabled="true" />
|
||||
<feature name="Blog Providers" enabled="true">
|
||||
<parameter name="supported" value="16B3FA3F-DAD7-4c93-A407-81CAE076883E;4AA58E69-8C24-40b1-BACE-3BB14237E8F9;556A165F-DA11-463c-BB4A-C77CC9047F22;B6F817C3-9D39-45c1-A634-EAC792B8A635;D48F1B5A-06E6-4f0f-BD76-74F34F520792;3C53BD64-4FA8-4bc7-8CB9-5D3BB58FAD07;B9C51DE3-F2DE-4204-AE7D-F64116429401;E0500CDC-91C9-4822-855F-CDC6F49B2735;CD671F17-1902-46e5-8266-A24D6C602234;82E6C828-8764-4af1-B289-647FC84E7093;CAA1EBAA-946E-4143-9733-F01CC8DBE827;D3405AD6-9E02-43b3-87F4-08F5A5946B40;E83A8A63-0F40-4499-AAD1-DFB2577CCC40;A82F57E0-41D3-45e9-861E-8C527844BCA6;494B3F2D-206D-449a-B0FA-A987E57C95D1;BAF4FE16-25FB-4a94-90C7-11A1B30CAD61;7D215B2D-7B57-432a-B382-ED027321C480;4FCDB0E2-66F8-4b4d-828B-B2EED6147789;785D4CDA-602F-481b-ACEA-3B3DB79B0F43;1AAFB2D8-F974-413A-9DBB-9C826CCDA7BC" />
|
||||
<parameter name="supported" value="16B3FA3F-DAD7-4c93-A407-81CAE076883E;4AA58E69-8C24-40b1-BACE-3BB14237E8F9;556A165F-DA11-463c-BB4A-C77CC9047F22;B6F817C3-9D39-45c1-A634-EAC792B8A635;D48F1B5A-06E6-4f0f-BD76-74F34F520792;3C53BD64-4FA8-4bc7-8CB9-5D3BB58FAD07;B9C51DE3-F2DE-4204-AE7D-F64116429401;E0500CDC-91C9-4822-855F-CDC6F49B2735;CD671F17-1902-46e5-8266-A24D6C602234;82E6C828-8764-4af1-B289-647FC84E7093;CAA1EBAA-946E-4143-9733-F01CC8DBE827;D3405AD6-9E02-43b3-87F4-08F5A5946B40;E83A8A63-0F40-4499-AAD1-DFB2577CCC40;A82F57E0-41D3-45e9-861E-8C527844BCA6;494B3F2D-206D-449a-B0FA-A987E57C95D1;BAF4FE16-25FB-4a94-90C7-11A1B30CAD61;7D215B2D-7B57-432a-B382-ED027321C480;4FCDB0E2-66F8-4b4d-828B-B2EED6147789;785D4CDA-602F-481b-ACEA-3B3DB79B0F43;1AAFB2D8-F974-413A-9DBB-9C826CCDA7BC;9E0EC9F9-AFDD-4812-9298-8963D5DB94CC" />
|
||||
</feature>
|
||||
<feature name="Tag Providers" enabled="true">
|
||||
<parameter name="supported" value="63DA9645-F182-4da7-88BC-B430ECA1AD75" />
|
||||
|
@ -78,4 +78,4 @@
|
|||
<parameter name="url" value="http://openlivewriter.org/WriterRedirect/CreateSpaceAddress" />
|
||||
</feature>
|
||||
</market>
|
||||
</features>
|
||||
</features>
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<?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="439"
|
||||
height="248"
|
||||
viewBox="0 0 116.15208 65.616669"
|
||||
version="1.1"
|
||||
id="svg4639"
|
||||
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
|
||||
sodipodi:docname="logotype.svg">
|
||||
<defs
|
||||
id="defs4633" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.98994949"
|
||||
inkscape:cx="27.052336"
|
||||
inkscape:cy="109.95002"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1027"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="635"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata4636">
|
||||
<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,-231.38331)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.40555573px;line-height:1.25;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.26458332"
|
||||
x="-0.61529404"
|
||||
y="293.91696"
|
||||
id="text5198"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan5196"
|
||||
x="-0.61529404"
|
||||
y="293.91696"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:13.40555573px;font-family:'Segoe UI';-inkscape-font-specification:'Segoe UI, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:-0.15875px;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;stroke-width:0.26458332">Open Live Writer</tspan></text>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
|
@ -27,7 +27,7 @@ namespace BlogRunner.Core
|
|||
|
||||
public void RunTests(Provider provider, Blog blog, XmlElement providerEl)
|
||||
{
|
||||
using (new BlogClientUIContextSilentMode()) //supress prompting for credentials
|
||||
using (new BlogClientUIContextSilentMode()) //suppress prompting for credentials
|
||||
{
|
||||
TemporaryBlogCredentials credentials = new TemporaryBlogCredentials();
|
||||
credentials.Username = blog.Username;
|
||||
|
|
|
@ -8,7 +8,19 @@
|
|||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Google.Apis" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="DeltaCompressionDotNet.MsDelta" publicKeyToken="46b2138a390abf55" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
|
|
@ -8,7 +8,19 @@
|
|||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Google.Apis" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="DeltaCompressionDotNet.MsDelta" publicKeyToken="46b2138a390abf55" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
|
|
@ -8,7 +8,19 @@
|
|||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-9.0.0.0" newVersion="9.0.0.0" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-10.0.0.0" newVersion="10.0.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Google.Apis" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="Google.Apis.Core" publicKeyToken="4b01fa6e34db77ab" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.39.0.0" newVersion="1.39.0.0" />
|
||||
</dependentAssembly>
|
||||
<dependentAssembly>
|
||||
<assemblyIdentity name="DeltaCompressionDotNet.MsDelta" publicKeyToken="46b2138a390abf55" culture="neutral" />
|
||||
<bindingRedirect oldVersion="0.0.0.0-1.1.0.0" newVersion="1.1.0.0" />
|
||||
</dependentAssembly>
|
||||
</assemblyBinding>
|
||||
</runtime>
|
||||
|
|
|
@ -6,3 +6,4 @@ using System.Reflection;
|
|||
[assembly: AssemblyCompany("Open Live Writer")]
|
||||
[assembly: AssemblyCopyright("Copyright (c) .NET Foundation. All rights reserved.")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyMetadata("SquirrelAwareVersion", "1")]
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<configuration>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
|
||||
</startup>
|
||||
</configuration>
|
|
@ -0,0 +1,89 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||
<ProjectGuid>{3B04E986-B74C-4151-8327-57F6BA8DECBC}</ProjectGuid>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<RootNamespace>LocEdit</RootNamespace>
|
||||
<AssemblyName>LocEdit</AssemblyName>
|
||||
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
|
||||
<FileAlignment>512</FileAlignment>
|
||||
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
|
||||
<Deterministic>true</Deterministic>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
<Optimize>false</Optimize>
|
||||
<OutputPath>bin\Debug\</OutputPath>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<PlatformTarget>AnyCPU</PlatformTarget>
|
||||
<DebugType>pdbonly</DebugType>
|
||||
<Optimize>true</Optimize>
|
||||
<OutputPath>bin\Release\</OutputPath>
|
||||
<DefineConstants>TRACE</DefineConstants>
|
||||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
<Reference Include="System.Data.DataSetExtensions" />
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
<Reference Include="System.Xml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="MainForm.cs">
|
||||
<SubType>Form</SubType>
|
||||
</Compile>
|
||||
<Compile Include="MainForm.Designer.cs">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</Compile>
|
||||
<Compile Include="Program.cs" />
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<EmbeddedResource Include="MainForm.resx">
|
||||
<DependentUpon>MainForm.cs</DependentUpon>
|
||||
</EmbeddedResource>
|
||||
<EmbeddedResource Include="Properties\Resources.resx">
|
||||
<Generator>ResXFileCodeGenerator</Generator>
|
||||
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
|
||||
<SubType>Designer</SubType>
|
||||
</EmbeddedResource>
|
||||
<Compile Include="Properties\Resources.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Resources.resx</DependentUpon>
|
||||
</Compile>
|
||||
<None Include="Properties\Settings.settings">
|
||||
<Generator>SettingsSingleFileGenerator</Generator>
|
||||
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
|
||||
</None>
|
||||
<Compile Include="Properties\Settings.Designer.cs">
|
||||
<AutoGen>True</AutoGen>
|
||||
<DependentUpon>Settings.settings</DependentUpon>
|
||||
<DesignTimeSharedInput>True</DesignTimeSharedInput>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="App.config" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\LocUtil\LocUtil.csproj">
|
||||
<Project>{e256f137-5de7-45aa-b51c-62616728401e}</Project>
|
||||
<Name>LocUtil</Name>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
|
@ -0,0 +1,279 @@
|
|||
namespace LocEdit
|
||||
{
|
||||
partial class MainForm
|
||||
{
|
||||
/// <summary>
|
||||
/// Required designer variable.
|
||||
/// </summary>
|
||||
private System.ComponentModel.IContainer components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Clean up any resources being used.
|
||||
/// </summary>
|
||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && (components != null))
|
||||
{
|
||||
components.Dispose();
|
||||
}
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
#region Windows Form Designer generated code
|
||||
|
||||
/// <summary>
|
||||
/// Required method for Designer support - do not modify
|
||||
/// the contents of this method with the code editor.
|
||||
/// </summary>
|
||||
private void InitializeComponent()
|
||||
{
|
||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
|
||||
System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
|
||||
this.toolStrip = new System.Windows.Forms.ToolStrip();
|
||||
this.toolStripButtonLoad = new System.Windows.Forms.ToolStripButton();
|
||||
this.toolStripButtonSave = new System.Windows.Forms.ToolStripButton();
|
||||
this.toolStripSeparator1 = new System.Windows.Forms.ToolStripSeparator();
|
||||
this.toolStripLabel1 = new System.Windows.Forms.ToolStripLabel();
|
||||
this.toolStripButtonInsertAbove = new System.Windows.Forms.ToolStripButton();
|
||||
this.toolStripButtonInsertBelow = new System.Windows.Forms.ToolStripButton();
|
||||
this.tableLayoutPanel1 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
|
||||
this.textBoxFind = new System.Windows.Forms.TextBox();
|
||||
this.buttonFind = new System.Windows.Forms.Button();
|
||||
this.label1 = new System.Windows.Forms.Label();
|
||||
this.dataGridView = new System.Windows.Forms.DataGridView();
|
||||
this.dataGridColKey = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridColValue = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.dataGridColComment = new System.Windows.Forms.DataGridViewTextBoxColumn();
|
||||
this.toolStrip.SuspendLayout();
|
||||
this.tableLayoutPanel1.SuspendLayout();
|
||||
this.tableLayoutPanel2.SuspendLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView)).BeginInit();
|
||||
this.SuspendLayout();
|
||||
//
|
||||
// toolStrip
|
||||
//
|
||||
this.toolStrip.ImageScalingSize = new System.Drawing.Size(28, 28);
|
||||
this.toolStrip.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
|
||||
this.toolStripButtonLoad,
|
||||
this.toolStripButtonSave,
|
||||
this.toolStripSeparator1,
|
||||
this.toolStripLabel1,
|
||||
this.toolStripButtonInsertAbove,
|
||||
this.toolStripButtonInsertBelow});
|
||||
this.toolStrip.Location = new System.Drawing.Point(0, 0);
|
||||
this.toolStrip.Name = "toolStrip";
|
||||
this.toolStrip.Padding = new System.Windows.Forms.Padding(0, 0, 2, 0);
|
||||
this.toolStrip.Size = new System.Drawing.Size(800, 25);
|
||||
this.toolStrip.TabIndex = 0;
|
||||
this.toolStrip.Text = "toolStrip";
|
||||
//
|
||||
// toolStripButtonLoad
|
||||
//
|
||||
this.toolStripButtonLoad.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButtonLoad.Image")));
|
||||
this.toolStripButtonLoad.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
|
||||
this.toolStripButtonLoad.ImageTransparentColor = System.Drawing.Color.Magenta;
|
||||
this.toolStripButtonLoad.Name = "toolStripButtonLoad";
|
||||
this.toolStripButtonLoad.Size = new System.Drawing.Size(56, 22);
|
||||
this.toolStripButtonLoad.Text = "Open";
|
||||
this.toolStripButtonLoad.Click += new System.EventHandler(this.ToolStripButtonLoad_Click);
|
||||
//
|
||||
// toolStripButtonSave
|
||||
//
|
||||
this.toolStripButtonSave.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButtonSave.Image")));
|
||||
this.toolStripButtonSave.ImageScaling = System.Windows.Forms.ToolStripItemImageScaling.None;
|
||||
this.toolStripButtonSave.ImageTransparentColor = System.Drawing.Color.Magenta;
|
||||
this.toolStripButtonSave.Name = "toolStripButtonSave";
|
||||
this.toolStripButtonSave.Size = new System.Drawing.Size(51, 22);
|
||||
this.toolStripButtonSave.Text = "Save";
|
||||
this.toolStripButtonSave.Click += new System.EventHandler(this.ToolStripButtonSave_Click);
|
||||
//
|
||||
// toolStripSeparator1
|
||||
//
|
||||
this.toolStripSeparator1.Name = "toolStripSeparator1";
|
||||
this.toolStripSeparator1.Size = new System.Drawing.Size(6, 25);
|
||||
//
|
||||
// toolStripLabel1
|
||||
//
|
||||
this.toolStripLabel1.ForeColor = System.Drawing.SystemColors.ControlDarkDark;
|
||||
this.toolStripLabel1.Name = "toolStripLabel1";
|
||||
this.toolStripLabel1.Size = new System.Drawing.Size(35, 22);
|
||||
this.toolStripLabel1.Text = "Rows";
|
||||
this.toolStripLabel1.Click += new System.EventHandler(this.ToolStripLabel1_Click);
|
||||
//
|
||||
// toolStripButtonInsertAbove
|
||||
//
|
||||
this.toolStripButtonInsertAbove.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.toolStripButtonInsertAbove.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButtonInsertAbove.Image")));
|
||||
this.toolStripButtonInsertAbove.ImageTransparentColor = System.Drawing.Color.Magenta;
|
||||
this.toolStripButtonInsertAbove.Name = "toolStripButtonInsertAbove";
|
||||
this.toolStripButtonInsertAbove.Size = new System.Drawing.Size(77, 22);
|
||||
this.toolStripButtonInsertAbove.Text = "Insert Above";
|
||||
this.toolStripButtonInsertAbove.Click += new System.EventHandler(this.ToolStripButtonInsertAbove_Click);
|
||||
//
|
||||
// toolStripButtonInsertBelow
|
||||
//
|
||||
this.toolStripButtonInsertBelow.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text;
|
||||
this.toolStripButtonInsertBelow.Image = ((System.Drawing.Image)(resources.GetObject("toolStripButtonInsertBelow.Image")));
|
||||
this.toolStripButtonInsertBelow.ImageTransparentColor = System.Drawing.Color.Magenta;
|
||||
this.toolStripButtonInsertBelow.Name = "toolStripButtonInsertBelow";
|
||||
this.toolStripButtonInsertBelow.Size = new System.Drawing.Size(75, 22);
|
||||
this.toolStripButtonInsertBelow.Text = "Insert Below";
|
||||
this.toolStripButtonInsertBelow.ToolTipText = "Insert Below";
|
||||
this.toolStripButtonInsertBelow.Click += new System.EventHandler(this.ToolStripButtonInsertBelow_Click);
|
||||
//
|
||||
// tableLayoutPanel1
|
||||
//
|
||||
this.tableLayoutPanel1.ColumnCount = 1;
|
||||
this.tableLayoutPanel1.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel1.Controls.Add(this.tableLayoutPanel2, 0, 1);
|
||||
this.tableLayoutPanel1.Controls.Add(this.dataGridView, 0, 0);
|
||||
this.tableLayoutPanel1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel1.Location = new System.Drawing.Point(0, 25);
|
||||
this.tableLayoutPanel1.Name = "tableLayoutPanel1";
|
||||
this.tableLayoutPanel1.RowCount = 2;
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel1.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.tableLayoutPanel1.Size = new System.Drawing.Size(800, 425);
|
||||
this.tableLayoutPanel1.TabIndex = 1;
|
||||
this.tableLayoutPanel1.Paint += new System.Windows.Forms.PaintEventHandler(this.TableLayoutPanel1_Paint);
|
||||
//
|
||||
// tableLayoutPanel2
|
||||
//
|
||||
this.tableLayoutPanel2.ColumnCount = 3;
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
|
||||
this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
|
||||
this.tableLayoutPanel2.Controls.Add(this.textBoxFind, 1, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.buttonFind, 2, 0);
|
||||
this.tableLayoutPanel2.Controls.Add(this.label1, 0, 0);
|
||||
this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 392);
|
||||
this.tableLayoutPanel2.Name = "tableLayoutPanel2";
|
||||
this.tableLayoutPanel2.RowCount = 1;
|
||||
this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
|
||||
this.tableLayoutPanel2.Size = new System.Drawing.Size(794, 30);
|
||||
this.tableLayoutPanel2.TabIndex = 0;
|
||||
//
|
||||
// textBoxFind
|
||||
//
|
||||
this.textBoxFind.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.textBoxFind.Location = new System.Drawing.Point(60, 3);
|
||||
this.textBoxFind.Name = "textBoxFind";
|
||||
this.textBoxFind.Size = new System.Drawing.Size(650, 20);
|
||||
this.textBoxFind.TabIndex = 0;
|
||||
this.textBoxFind.KeyUp += new System.Windows.Forms.KeyEventHandler(this.TextBoxFind_KeyUp);
|
||||
//
|
||||
// buttonFind
|
||||
//
|
||||
this.buttonFind.Location = new System.Drawing.Point(716, 3);
|
||||
this.buttonFind.Name = "buttonFind";
|
||||
this.buttonFind.Size = new System.Drawing.Size(75, 23);
|
||||
this.buttonFind.TabIndex = 1;
|
||||
this.buttonFind.Text = "Next (F3)";
|
||||
this.buttonFind.UseVisualStyleBackColor = true;
|
||||
this.buttonFind.Click += new System.EventHandler(this.ButtonFind_Click);
|
||||
//
|
||||
// label1
|
||||
//
|
||||
this.label1.AutoSize = true;
|
||||
this.label1.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.label1.Location = new System.Drawing.Point(3, 0);
|
||||
this.label1.Name = "label1";
|
||||
this.label1.Size = new System.Drawing.Size(51, 30);
|
||||
this.label1.TabIndex = 2;
|
||||
this.label1.Text = "Find Key:";
|
||||
this.label1.TextAlign = System.Drawing.ContentAlignment.MiddleCenter;
|
||||
//
|
||||
// dataGridView
|
||||
//
|
||||
this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
|
||||
this.dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
|
||||
this.dataGridColKey,
|
||||
this.dataGridColValue,
|
||||
this.dataGridColComment});
|
||||
dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
|
||||
dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Window;
|
||||
dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
|
||||
dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.ControlText;
|
||||
dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
|
||||
dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
|
||||
dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
|
||||
this.dataGridView.DefaultCellStyle = dataGridViewCellStyle1;
|
||||
this.dataGridView.Dock = System.Windows.Forms.DockStyle.Fill;
|
||||
this.dataGridView.Location = new System.Drawing.Point(3, 3);
|
||||
this.dataGridView.MultiSelect = false;
|
||||
this.dataGridView.Name = "dataGridView";
|
||||
this.dataGridView.RowHeadersWidth = 72;
|
||||
this.dataGridView.Size = new System.Drawing.Size(794, 383);
|
||||
this.dataGridView.TabIndex = 1;
|
||||
this.dataGridView.CellValueChanged += new System.Windows.Forms.DataGridViewCellEventHandler(this.DataGridView_CellValueChanged);
|
||||
//
|
||||
// dataGridColKey
|
||||
//
|
||||
this.dataGridColKey.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
|
||||
this.dataGridColKey.HeaderText = "Key";
|
||||
this.dataGridColKey.MinimumWidth = 9;
|
||||
this.dataGridColKey.Name = "dataGridColKey";
|
||||
this.dataGridColKey.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
//
|
||||
// dataGridColValue
|
||||
//
|
||||
this.dataGridColValue.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
|
||||
this.dataGridColValue.HeaderText = "Value";
|
||||
this.dataGridColValue.MinimumWidth = 9;
|
||||
this.dataGridColValue.Name = "dataGridColValue";
|
||||
this.dataGridColValue.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
//
|
||||
// dataGridColComment
|
||||
//
|
||||
this.dataGridColComment.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
|
||||
this.dataGridColComment.HeaderText = "Comment";
|
||||
this.dataGridColComment.MinimumWidth = 9;
|
||||
this.dataGridColComment.Name = "dataGridColComment";
|
||||
this.dataGridColComment.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
|
||||
//
|
||||
// MainForm
|
||||
//
|
||||
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
|
||||
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
|
||||
this.ClientSize = new System.Drawing.Size(800, 450);
|
||||
this.Controls.Add(this.tableLayoutPanel1);
|
||||
this.Controls.Add(this.toolStrip);
|
||||
this.KeyPreview = true;
|
||||
this.Name = "MainForm";
|
||||
this.Text = "LocEdit";
|
||||
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.MainForm_FormClosing);
|
||||
this.toolStrip.ResumeLayout(false);
|
||||
this.toolStrip.PerformLayout();
|
||||
this.tableLayoutPanel1.ResumeLayout(false);
|
||||
this.tableLayoutPanel2.ResumeLayout(false);
|
||||
this.tableLayoutPanel2.PerformLayout();
|
||||
((System.ComponentModel.ISupportInitialize)(this.dataGridView)).EndInit();
|
||||
this.ResumeLayout(false);
|
||||
this.PerformLayout();
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private System.Windows.Forms.ToolStrip toolStrip;
|
||||
private System.Windows.Forms.ToolStripButton toolStripButtonLoad;
|
||||
private System.Windows.Forms.ToolStripButton toolStripButtonSave;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel1;
|
||||
private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
|
||||
private System.Windows.Forms.TextBox textBoxFind;
|
||||
private System.Windows.Forms.Button buttonFind;
|
||||
private System.Windows.Forms.DataGridView dataGridView;
|
||||
private System.Windows.Forms.Label label1;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridColKey;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridColValue;
|
||||
private System.Windows.Forms.DataGridViewTextBoxColumn dataGridColComment;
|
||||
private System.Windows.Forms.ToolStripSeparator toolStripSeparator1;
|
||||
private System.Windows.Forms.ToolStripButton toolStripButtonInsertAbove;
|
||||
private System.Windows.Forms.ToolStripButton toolStripButtonInsertBelow;
|
||||
private System.Windows.Forms.ToolStripLabel toolStripLabel1;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,252 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Data;
|
||||
using System.Drawing;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
using LocUtil;
|
||||
|
||||
namespace LocEdit
|
||||
{
|
||||
public partial class MainForm : Form
|
||||
{
|
||||
private string _loadedFile;
|
||||
|
||||
public MainForm()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private bool _modified = false;
|
||||
public bool Modified
|
||||
{
|
||||
get => _modified;
|
||||
set
|
||||
{
|
||||
_modified = value;
|
||||
Text = $"LocEdit: {_loadedFile}{(_modified ? "*" : "")}";
|
||||
}
|
||||
}
|
||||
|
||||
public void LoadFile(string filePath)
|
||||
{
|
||||
_loadedFile = filePath;
|
||||
Modified = false;
|
||||
|
||||
dataGridView.Rows.Clear();
|
||||
using (CsvParser csvParser = new CsvParser(new StreamReader(new FileStream(Path.GetFullPath(filePath), FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.Default), true))
|
||||
{
|
||||
foreach (string[] line in csvParser)
|
||||
{
|
||||
string value = line[1];
|
||||
value = value.Replace(((char)8230) + "", "..."); // undo ellipses
|
||||
string comment = (line.Length > 2) ? line[2] : "";
|
||||
|
||||
dataGridView.Rows.Add(new LocDataGridViewRow(line[0], value, comment));
|
||||
}
|
||||
}
|
||||
dataGridView.AutoResizeRows();
|
||||
|
||||
}
|
||||
|
||||
public void SaveFile(string filePath)
|
||||
{
|
||||
_loadedFile = filePath;
|
||||
Modified = false;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("Name,Value,Comment");
|
||||
|
||||
foreach(DataGridViewRow row in dataGridView.Rows)
|
||||
{
|
||||
if (row.Cells.Count < 3) continue;
|
||||
|
||||
var key = (string)row.Cells[0].Value;
|
||||
var value = (string)row.Cells[1].Value;
|
||||
var comment = (string)row.Cells[2].Value;
|
||||
|
||||
if (key == null || value == null) continue;
|
||||
|
||||
sb.Append($"{Helpers.CsvizeString(key)},");
|
||||
sb.Append($"{Helpers.CsvizeString(value)},");
|
||||
sb.Append($"{Helpers.CsvizeString(comment == null ? "" : comment)}");
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
File.WriteAllText(filePath, sb.ToString(), Encoding.Default);
|
||||
}
|
||||
|
||||
public void FindNext(string query)
|
||||
{
|
||||
int y = dataGridView.CurrentCell != null ? dataGridView.CurrentCell.RowIndex + 1 : 0;
|
||||
dataGridView.ClearSelection();
|
||||
|
||||
while(y < dataGridView.Rows.Count - 1)
|
||||
{
|
||||
if (((string)dataGridView.Rows[y].Cells[0].Value).ToLower().Contains(query.ToLower()))
|
||||
{
|
||||
dataGridView.CurrentCell = dataGridView.Rows[y].Cells[0];
|
||||
return;
|
||||
}
|
||||
y++;
|
||||
}
|
||||
|
||||
MessageBox.Show("No results found, returning to start.");
|
||||
if (dataGridView.Rows.Count == 0 || dataGridView.Rows[0].Cells.Count == 0) return;
|
||||
|
||||
dataGridView.CurrentCell = dataGridView.Rows[0].Cells[0];
|
||||
}
|
||||
|
||||
protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
|
||||
{
|
||||
if (keyData == (Keys.Control | Keys.O))
|
||||
{
|
||||
ShowOpenDialog();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyData == (Keys.Control | Keys.S))
|
||||
{
|
||||
ShowSaveDialog();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyData == (Keys.Control | Keys.F))
|
||||
{
|
||||
textBoxFind.Focus();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyData == (Keys.Control | Keys.A))
|
||||
{
|
||||
if(dataGridView.CurrentRow != null) dataGridView.CurrentRow.Selected = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyData == Keys.F3)
|
||||
{
|
||||
FindNext(textBoxFind.Text);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.ProcessCmdKey(ref msg, keyData);
|
||||
}
|
||||
|
||||
private void ShowOpenDialog()
|
||||
{
|
||||
using (OpenFileDialog openFileDialog1 = new OpenFileDialog())
|
||||
{
|
||||
openFileDialog1.Filter = "Localization CSV File (*.csv)|*.csv";
|
||||
|
||||
if (openFileDialog1.ShowDialog() == DialogResult.OK) LoadFile(openFileDialog1.FileName);
|
||||
}
|
||||
}
|
||||
|
||||
private void ShowSaveDialog()
|
||||
{
|
||||
using (SaveFileDialog saveFileDialog1 = new SaveFileDialog())
|
||||
{
|
||||
saveFileDialog1.FileName = _loadedFile;
|
||||
saveFileDialog1.Filter = "Localization CSV File (*.csv)|*.csv";
|
||||
|
||||
if (saveFileDialog1.ShowDialog() == DialogResult.OK) SaveFile(saveFileDialog1.FileName);
|
||||
}
|
||||
}
|
||||
|
||||
private void TableLayoutPanel1_Paint(object sender, PaintEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void ToolStripButtonLoad_Click(object sender, EventArgs e)
|
||||
=> ShowOpenDialog();
|
||||
|
||||
private void ButtonFind_Click(object sender, EventArgs e)
|
||||
=> FindNext(textBoxFind.Text);
|
||||
|
||||
private void TextBoxFind_KeyUp(object sender, KeyEventArgs e)
|
||||
{
|
||||
if (textBoxFind.Focused && e.KeyCode == Keys.Enter)
|
||||
{
|
||||
FindNext(textBoxFind.Text);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void ToolStripButtonSave_Click(object sender, EventArgs e)
|
||||
=> ShowSaveDialog();
|
||||
|
||||
private void DataGridView_CellValueChanged(object sender, DataGridViewCellEventArgs e)
|
||||
=> Modified = true;
|
||||
|
||||
private void MainForm_FormClosing(object sender, FormClosingEventArgs e)
|
||||
{
|
||||
if (!Modified) return;
|
||||
|
||||
var result = MessageBox.Show(this, "There are unsaved changes. Are you sure you want to exit?", "LocEdit", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation);
|
||||
if (result == DialogResult.No) e.Cancel = true;
|
||||
}
|
||||
|
||||
private void ToolStripLabel1_Click(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void ToolStripButtonInsertAbove_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (dataGridView.CurrentCell != null) dataGridView.Rows.Insert(dataGridView.CurrentCell.RowIndex);
|
||||
}
|
||||
|
||||
private void ToolStripButtonInsertBelow_Click(object sender, EventArgs e)
|
||||
{
|
||||
if (dataGridView.CurrentCell != null && dataGridView.CurrentCell.RowIndex < dataGridView.Rows.Count - 1)
|
||||
dataGridView.Rows.Insert(dataGridView.CurrentCell.RowIndex + 1);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class Helpers
|
||||
{
|
||||
public static string CsvizeString(string input)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
bool shouldQuote = input.Contains(",") || input.Contains("\n") || (input != string.Empty && input[0] == '"');
|
||||
|
||||
if (shouldQuote)
|
||||
{
|
||||
sb.Append('"');
|
||||
sb.Append(input.Replace("\"", "\"\"")); // Replace double-quotes with double-double-quotes
|
||||
sb.Append('"');
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.Append(input);
|
||||
}
|
||||
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocDataGridViewRow : DataGridViewRow
|
||||
{
|
||||
private DataGridViewCell keyCell;
|
||||
private DataGridViewCell valueCell;
|
||||
private DataGridViewCell commentCell;
|
||||
|
||||
public LocDataGridViewRow(string key, string value, string comment) : base()
|
||||
{
|
||||
keyCell = new DataGridViewTextBoxCell() { Value = key };
|
||||
valueCell = new DataGridViewTextBoxCell() { Value = value };
|
||||
commentCell = new DataGridViewTextBoxCell() { Value = comment };
|
||||
|
||||
Cells.Add(keyCell);
|
||||
Cells.Add(valueCell);
|
||||
Cells.Add(commentCell);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<metadata name="toolStrip.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
|
||||
<value>17, 17</value>
|
||||
</metadata>
|
||||
<assembly alias="System.Drawing" name="System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
|
||||
<data name="toolStripButtonLoad.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIwSURBVDhPzZBdSFNhGMdfhK7Cm+oquu2yr8voKi/KsiJE
|
||||
SBAjWOrm3HLp1haxGVnBloNMW9qKjCYK1QqXgRhNKdpyzekispVMu4oRrvZ98px/z3t2pkURddcDP97z
|
||||
vOf9/87zHvZ/VHS4o55AiXePHIgOnT2ovGaSJG0URdEhLIug9Q71Fcp+OYsM2ioig1YgG1khtTAGvkeS
|
||||
3fwgD9KyltiQSCS0vKfwTpLdYONXDFE5mJr4CTEVkiUcf1+7vMZGu+B3GY7G43FZIgjCIhvrbYWUCUP6
|
||||
8uRX+P4PpBcfYH7ChUKh4Esmk1kSfGSPu/WQvgYhfvb9FVO3T/PrlBPriXVsxNkMMfkMc8+dCDw8A2+/
|
||||
VqGliKsB3mualf5WZz2cluoRjnrf1gPsvr0R3xLj8Lk1SL5xQVpwE9cV+PPvudfTkG6q2r6ZDZ9XIfai
|
||||
B5MeDZY/dCMXNiA3fRL56TbkI+0ozJ6SEaJmwiKzFGiDumqbw2ZjZWzAWpf33dRjzn8BhdcdyL46QbSu
|
||||
iiJGFGaIkmjWjMhdFQl21NI/YOyq6cjSqPs4hNhlZCicmWpBJqQjiV6W5eRJCC6ZMcl4Og/Ny+Pz6jXX
|
||||
vX86ZITw1o50UIP0y2ZCuyoKF6cpXenTpA4UtsthXn3WY31Br4m+ZkIqoCaakAqqSVKUZULKJIqo31jN
|
||||
BTVKnDFPl3n/gO0wLjZW4pxqzx+5pN0Lc+0ui65yyyYlzlgoFFoDoExp/7EY+w5ZLEQ8FqWLzwAAAABJ
|
||||
RU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="toolStripButtonSave.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAF9SURBVDhPY6AasA2vnGcNxFVVVWBcWVk5r7y8HAPreZWA
|
||||
MVQbBJgGlM+bsefF/6wZh//jAy9fvvpfMOvc/7oVd/+jGGIcVD5v2u4X/wvnHoUqxQ5gBtSuuPNfzxfJ
|
||||
AFOgASAXlC8+9f/nr184McyAmmVAA7zyEQaAnNOz+en/zOmH/n/79g0nhhlQvvjGfw3XXFQDOjc9+Z86
|
||||
ZT9WjTAMM6B04Y3/yk7pqAa0r3v0v33Do/+r99/8vwIPBmkumX/9v5wtmgGt6x7+b1rz4H/j6gf/G1bd
|
||||
B4b0vf+1y+/+rwEGWPXS22Bnly6AaC6ed+2/nHUqqgFwzStAmu+CnYqOezYcA2sumncV6IIkVAPAmlfe
|
||||
A2vO692KNQyCazeBMdgAazQD6qGaQXEMMyCmbTfYZqIMgGkGxTHMAJjTiTIApLl6+Z3/VUtuE/RCITgM
|
||||
UuYBEycj1AgGBlmr5GY56+TpQHouyHRcWMYqaaacZUqbeVCean19PRNUO7mAgQEAVYgriv4250IAAAAA
|
||||
SUVORK5CYII=
|
||||
</value>
|
||||
</data>
|
||||
<data name="toolStripButtonInsertAbove.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
|
||||
YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
|
||||
0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
|
||||
bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
|
||||
VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
|
||||
c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
|
||||
Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
|
||||
mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
|
||||
kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
|
||||
TgDQASA1MVpwzwAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<data name="toolStripButtonInsertBelow.Image" type="System.Drawing.Bitmap, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>
|
||||
iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8
|
||||
YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAIDSURBVDhPpZLrS5NhGMb3j4SWh0oRQVExD4gonkDpg4hG
|
||||
YKxG6WBogkMZKgPNCEVJFBGdGETEvgwyO9DJE5syZw3PIlPEE9pgBCLZ5XvdMB8Ew8gXbl54nuf63dd9
|
||||
0OGSnwCahxbPRNPAPMw9Xpg6ZmF46kZZ0xSKzJPIrhpDWsVnpBhGkKx3nAX8Pv7z1zg8OoY/cITdn4fw
|
||||
bf/C0kYAN3Ma/w3gWfZL5kzTKBxjWyK2DftwI9tyMYCZKXbNHaD91bLYJrDXsYbrWfUKwJrPE9M2M1Oc
|
||||
VzOOpHI7Jr376Hi9ogHqFIANO0/MmmmbmSmm9a8ze+I4MrNWAdjtoJgWcx+PSzg166yZZ8xM8XvXDix9
|
||||
c4jIqFYAjoriBV9AhEPv1mH/sonogha0afbZMMZz+yreTGyhpusHwtNNCsA5U1zS4BLxzJIfg299qO32
|
||||
Ir7UJtZfftyATqeT+8o2D8JSjQrAJblrncYL7ZJ2+bfaFnC/1S1NjL3diRat7qrO7wLRP3HjWsojBeCo
|
||||
mDEo5mNjuweFGvjWg2EBhCbpkW78htSHHwRyNdmgAFzPEee2iFkzayy2OLXzT4gr6UdUnlXrullsxxQ+
|
||||
kx0g8BTA3aZlButjSTyjODq/WcQcW/B/Je4OQhLvKQDnzN1mp0nnkvAhR8VuMzNrpm1mpjgkoVwB/v8D
|
||||
TgDQASA1MVpwzwAAAABJRU5ErkJggg==
|
||||
</value>
|
||||
</data>
|
||||
<metadata name="dataGridColKey.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="dataGridColValue.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="dataGridColComment.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>True</value>
|
||||
</metadata>
|
||||
<metadata name="$this.TrayHeight" type="System.Int32, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
|
||||
<value>51</value>
|
||||
</metadata>
|
||||
</root>
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
|
||||
namespace LocEdit
|
||||
{
|
||||
static class Program
|
||||
{
|
||||
/// <summary>
|
||||
/// The main entry point for the application.
|
||||
/// </summary>
|
||||
[STAThread]
|
||||
static void Main()
|
||||
{
|
||||
Application.EnableVisualStyles();
|
||||
Application.SetCompatibleTextRenderingDefault(false);
|
||||
Application.Run(new MainForm());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
// General Information about an assembly is controlled through the following
|
||||
// set of attributes. Change these attribute values to modify the information
|
||||
// associated with an assembly.
|
||||
[assembly: AssemblyTitle("LocEdit")]
|
||||
[assembly: AssemblyDescription("")]
|
||||
[assembly: AssemblyConfiguration("")]
|
||||
[assembly: AssemblyCompany("")]
|
||||
[assembly: AssemblyProduct("LocEdit")]
|
||||
[assembly: AssemblyCopyright("Copyright © 2019")]
|
||||
[assembly: AssemblyTrademark("")]
|
||||
[assembly: AssemblyCulture("")]
|
||||
|
||||
// Setting ComVisible to false makes the types in this assembly not visible
|
||||
// to COM components. If you need to access a type in this assembly from
|
||||
// COM, set the ComVisible attribute to true on that type.
|
||||
[assembly: ComVisible(false)]
|
||||
|
||||
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||
[assembly: Guid("3b04e986-b74c-4151-8327-57f6ba8decbc")]
|
||||
|
||||
// Version information for an assembly consists of the following four values:
|
||||
//
|
||||
// Major Version
|
||||
// Minor Version
|
||||
// Build Number
|
||||
// Revision
|
||||
//
|
||||
// You can specify all the values or you can default the Build and Revision Numbers
|
||||
// by using the '*' as shown below:
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("1.0.0.0")]
|
||||
[assembly: AssemblyFileVersion("1.0.0.0")]
|
|
@ -0,0 +1,71 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace LocEdit.Properties
|
||||
{
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// A strongly-typed resource class, for looking up localized strings, etc.
|
||||
/// </summary>
|
||||
// This class was auto-generated by the StronglyTypedResourceBuilder
|
||||
// class via a tool like ResGen or Visual Studio.
|
||||
// To add or remove a member, edit your .ResX file then rerun ResGen
|
||||
// with the /str option, or rebuild your VS project.
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
|
||||
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
internal class Resources
|
||||
{
|
||||
|
||||
private static global::System.Resources.ResourceManager resourceMan;
|
||||
|
||||
private static global::System.Globalization.CultureInfo resourceCulture;
|
||||
|
||||
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
|
||||
internal Resources()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the cached ResourceManager instance used by this class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Resources.ResourceManager ResourceManager
|
||||
{
|
||||
get
|
||||
{
|
||||
if ((resourceMan == null))
|
||||
{
|
||||
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("LocEdit.Properties.Resources", typeof(Resources).Assembly);
|
||||
resourceMan = temp;
|
||||
}
|
||||
return resourceMan;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overrides the current thread's CurrentUICulture property for all
|
||||
/// resource lookups using this strongly typed resource class.
|
||||
/// </summary>
|
||||
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
|
||||
internal static global::System.Globalization.CultureInfo Culture
|
||||
{
|
||||
get
|
||||
{
|
||||
return resourceCulture;
|
||||
}
|
||||
set
|
||||
{
|
||||
resourceCulture = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<root>
|
||||
<!--
|
||||
Microsoft ResX Schema
|
||||
|
||||
Version 2.0
|
||||
|
||||
The primary goals of this format is to allow a simple XML format
|
||||
that is mostly human readable. The generation and parsing of the
|
||||
various data types are done through the TypeConverter classes
|
||||
associated with the data types.
|
||||
|
||||
Example:
|
||||
|
||||
... ado.net/XML headers & schema ...
|
||||
<resheader name="resmimetype">text/microsoft-resx</resheader>
|
||||
<resheader name="version">2.0</resheader>
|
||||
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
|
||||
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
|
||||
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
|
||||
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
|
||||
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
|
||||
<value>[base64 mime encoded serialized .NET Framework object]</value>
|
||||
</data>
|
||||
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
|
||||
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
|
||||
<comment>This is a comment</comment>
|
||||
</data>
|
||||
|
||||
There are any number of "resheader" rows that contain simple
|
||||
name/value pairs.
|
||||
|
||||
Each data row contains a name, and value. The row also contains a
|
||||
type or mimetype. Type corresponds to a .NET class that support
|
||||
text/value conversion through the TypeConverter architecture.
|
||||
Classes that don't support this are serialized and stored with the
|
||||
mimetype set.
|
||||
|
||||
The mimetype is used for serialized objects, and tells the
|
||||
ResXResourceReader how to depersist the object. This is currently not
|
||||
extensible. For a given mimetype the value must be set accordingly:
|
||||
|
||||
Note - application/x-microsoft.net.object.binary.base64 is the format
|
||||
that the ResXResourceWriter will generate, however the reader can
|
||||
read any of the formats listed below.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.binary.base64
|
||||
value : The object must be serialized with
|
||||
: System.Serialization.Formatters.Binary.BinaryFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.soap.base64
|
||||
value : The object must be serialized with
|
||||
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
|
||||
: and then encoded with base64 encoding.
|
||||
|
||||
mimetype: application/x-microsoft.net.object.bytearray.base64
|
||||
value : The object must be serialized into a byte array
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:schema>
|
||||
<resheader name="resmimetype">
|
||||
<value>text/microsoft-resx</value>
|
||||
</resheader>
|
||||
<resheader name="version">
|
||||
<value>2.0</value>
|
||||
</resheader>
|
||||
<resheader name="reader">
|
||||
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
</root>
|
|
@ -0,0 +1,30 @@
|
|||
//------------------------------------------------------------------------------
|
||||
// <auto-generated>
|
||||
// This code was generated by a tool.
|
||||
// Runtime Version:4.0.30319.42000
|
||||
//
|
||||
// Changes to this file may cause incorrect behavior and will be lost if
|
||||
// the code is regenerated.
|
||||
// </auto-generated>
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
namespace LocEdit.Properties
|
||||
{
|
||||
|
||||
|
||||
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
|
||||
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")]
|
||||
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
|
||||
{
|
||||
|
||||
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
|
||||
|
||||
public static Settings Default
|
||||
{
|
||||
get
|
||||
{
|
||||
return defaultInstance;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<SettingsFile xmlns="http://schemas.microsoft.com/VisualStudio/2004/01/settings" CurrentProfile="(Default)">
|
||||
<Profiles>
|
||||
<Profile Name="(Default)" />
|
||||
</Profiles>
|
||||
<Settings />
|
||||
</SettingsFile>
|
|
@ -81,79 +81,83 @@ namespace LocUtil
|
|||
string[] commandFiles = (string[])ArrayHelper.Narrow(clo.GetValues("c"), typeof(string));
|
||||
string[] dialogFiles = (string[])ArrayHelper.Narrow(clo.GetValues("d"), typeof(string));
|
||||
string[] ribbonFiles = (string[])ArrayHelper.Narrow(clo.GetValues("r"), typeof(string));
|
||||
string[] stringFiles = (string[])ArrayHelper.Narrow(clo.GetValues("s"), typeof(string));
|
||||
|
||||
if (commandFiles.Length + dialogFiles.Length == 0)
|
||||
if (commandFiles.Length + dialogFiles.Length + stringFiles.Length == 0)
|
||||
{
|
||||
Console.Error.WriteLine("No input files were specified.");
|
||||
return 1;
|
||||
}
|
||||
|
||||
HashSet ribbonIds;
|
||||
Hashtable ribbonValues;
|
||||
Console.WriteLine("Parsing commands from " + StringHelper.Join(commandFiles, ";"));
|
||||
if (!ParseRibbonXml(ribbonFiles, pairsLoc, pairsNonLoc, typeof(Command), "//ribbon:Command", "Command.{0}.{1}", out ribbonIds, out ribbonValues))
|
||||
return 1;
|
||||
HashSet commandIds;
|
||||
Console.WriteLine("Parsing commands from " + StringHelper.Join(commandFiles, ";"));
|
||||
|
||||
string[] transformedCommandFiles = commandFiles;
|
||||
try
|
||||
if(commandFiles.Length + dialogFiles.Length > 0)
|
||||
{
|
||||
// Transform the files
|
||||
XslCompiledTransform xslTransform = new XslCompiledTransform(true);
|
||||
string xslFile = Path.GetFullPath("Commands.xsl");
|
||||
HashSet ribbonIds;
|
||||
Hashtable ribbonValues;
|
||||
Console.WriteLine("Parsing commands from " + StringHelper.Join(commandFiles, ";"));
|
||||
if (!ParseRibbonXml(ribbonFiles, pairsLoc, pairsNonLoc, typeof(Command), "//ribbon:Command", "Command.{0}.{1}", out ribbonIds, out ribbonValues))
|
||||
return 1;
|
||||
HashSet commandIds;
|
||||
Console.WriteLine("Parsing commands from " + StringHelper.Join(commandFiles, ";"));
|
||||
|
||||
for (int i = 0; i < commandFiles.Length; i++)//string filename in commandFiles)
|
||||
string[] transformedCommandFiles = commandFiles;
|
||||
try
|
||||
{
|
||||
string inputFile = Path.GetFullPath(commandFiles[i]);
|
||||
if (!File.Exists(inputFile))
|
||||
throw new ConfigurationErrorsException("File not found: " + inputFile);
|
||||
// Transform the files
|
||||
XslCompiledTransform xslTransform = new XslCompiledTransform(true);
|
||||
string xslFile = Path.GetFullPath("Commands.xsl");
|
||||
|
||||
xslTransform.Load(xslFile);
|
||||
for (int i = 0; i < commandFiles.Length; i++)//string filename in commandFiles)
|
||||
{
|
||||
string inputFile = Path.GetFullPath(commandFiles[i]);
|
||||
if (!File.Exists(inputFile))
|
||||
throw new ConfigurationErrorsException("File not found: " + inputFile);
|
||||
|
||||
string transformedFile = inputFile.Replace(".xml", ".transformed.xml");
|
||||
xslTransform.Transform(inputFile, transformedFile);
|
||||
transformedCommandFiles[i] = transformedFile;
|
||||
xslTransform.Load(xslFile);
|
||||
|
||||
string transformedFile = inputFile.Replace(".xml", ".transformed.xml");
|
||||
xslTransform.Transform(inputFile, transformedFile);
|
||||
transformedCommandFiles[i] = transformedFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine("Failed to transform file: " + ex);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!ParseCommandXml(transformedCommandFiles, pairsLoc, pairsNonLoc, typeof(Command), "/Commands/Command", "Command.{0}.{1}", out commandIds))
|
||||
return 1;
|
||||
HashSet dialogIds;
|
||||
Console.WriteLine("Parsing messages from " + StringHelper.Join(dialogFiles, ";"));
|
||||
if (!ParseCommandXml(dialogFiles, pairsLoc, pairsNonLoc, typeof(DisplayMessage), "/Messages/Message", "DisplayMessage.{0}.{1}", out dialogIds))
|
||||
return 1;
|
||||
|
||||
string propsFile = (string)clo.GetValue("props", null);
|
||||
Console.WriteLine("Writing localizable resources to " + propsFile);
|
||||
WritePairs(pairsLoc, propsFile, true);
|
||||
|
||||
string propsNonLocFile = (string)clo.GetValue("propsnonloc", null);
|
||||
Console.WriteLine("Writing non-localizable resources to " + propsNonLocFile);
|
||||
WritePairs(pairsNonLoc, propsNonLocFile, false);
|
||||
|
||||
if (clo.IsArgPresent("cenum"))
|
||||
{
|
||||
string cenum = (string)clo.GetValue("cenum", null);
|
||||
Console.WriteLine("Generating CommandId enum file " + cenum);
|
||||
|
||||
// commandId: command name
|
||||
// ribbonValues: command name --> resource id
|
||||
commandIds.AddAll(ribbonIds);
|
||||
if (!GenerateEnum(commandIds, "CommandId", cenum, null, ribbonValues))
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.Error.WriteLine("Failed to transform file: " + ex);
|
||||
return 1;
|
||||
}
|
||||
if (clo.IsArgPresent("denum"))
|
||||
{
|
||||
string denum = (string)clo.GetValue("denum", null);
|
||||
Console.WriteLine("Generating MessageId enum file " + denum);
|
||||
if (!GenerateEnum(dialogIds, "MessageId", denum, null, null))
|
||||
}
|
||||
|
||||
if (!ParseCommandXml(transformedCommandFiles, pairsLoc, pairsNonLoc, typeof(Command), "/Commands/Command", "Command.{0}.{1}", out commandIds))
|
||||
return 1;
|
||||
HashSet dialogIds;
|
||||
Console.WriteLine("Parsing messages from " + StringHelper.Join(dialogFiles, ";"));
|
||||
if (!ParseCommandXml(dialogFiles, pairsLoc, pairsNonLoc, typeof(DisplayMessage), "/Messages/Message", "DisplayMessage.{0}.{1}", out dialogIds))
|
||||
return 1;
|
||||
|
||||
string propsFile = (string)clo.GetValue("props", null);
|
||||
Console.WriteLine("Writing localizable resources to " + propsFile);
|
||||
WritePairs(pairsLoc, propsFile, true);
|
||||
|
||||
string propsNonLocFile = (string)clo.GetValue("propsnonloc", null);
|
||||
Console.WriteLine("Writing non-localizable resources to " + propsNonLocFile);
|
||||
WritePairs(pairsNonLoc, propsNonLocFile, false);
|
||||
|
||||
if (clo.IsArgPresent("cenum"))
|
||||
{
|
||||
string cenum = (string)clo.GetValue("cenum", null);
|
||||
Console.WriteLine("Generating CommandId enum file " + cenum);
|
||||
|
||||
// commandId: command name
|
||||
// ribbonValues: command name --> resource id
|
||||
commandIds.AddAll(ribbonIds);
|
||||
if (!GenerateEnum(commandIds, "CommandId", cenum, null, ribbonValues))
|
||||
return 1;
|
||||
}
|
||||
if (clo.IsArgPresent("denum"))
|
||||
{
|
||||
string denum = (string)clo.GetValue("denum", null);
|
||||
Console.WriteLine("Generating MessageId enum file " + denum);
|
||||
if (!GenerateEnum(dialogIds, "MessageId", denum, null, null))
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (clo.IsArgPresent("s"))
|
||||
|
@ -278,6 +282,13 @@ namespace LocUtil
|
|||
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.LoadXml(xmlBuffer.ToString());
|
||||
|
||||
// Add auto-generation note
|
||||
xmlDoc.GetElementsByTagName("root")[0].PrependChild(xmlDoc.CreateComment(@"
|
||||
This file is automatically generated. DO NOT edit it manually.
|
||||
Edit the relevant XML or CSV files, and run LocUtil.
|
||||
A Batch file is provided in the repository root for easy regeneration of the strings tables."));
|
||||
|
||||
foreach (XmlElement dataNode in xmlDoc.SelectNodes("/root/data"))
|
||||
{
|
||||
string name = dataNode.GetAttribute("name");
|
||||
|
@ -293,21 +304,37 @@ namespace LocUtil
|
|||
}
|
||||
}
|
||||
}
|
||||
xmlDoc.Save(path);
|
||||
|
||||
// Correct the formatting as to not create needlessly large diffs
|
||||
var sb = new StringBuilder();
|
||||
var stringWriter = new Utf8StringWriter(sb);
|
||||
xmlDoc.Save(stringWriter);
|
||||
File.WriteAllText(path,
|
||||
sb.ToString()
|
||||
.Replace(" <comment>", " <comment>") // Fix comment tag indent
|
||||
.Replace("</comment></data>", "</comment>\r\n </data>"), // Move data close following comment close onto own line
|
||||
Encoding.UTF8);
|
||||
}
|
||||
|
||||
// @RIBBON TODO: For now the union of the command in Commands.xml and Ribbon.xml will go into the CommandId enum.
|
||||
private static bool GenerateEnum(HashSet commandIds, string enumName, string enumPath, Hashtable descriptions, Hashtable values)
|
||||
{
|
||||
const string TEMPLATE = @"namespace OpenLiveWriter.Localization
|
||||
{{
|
||||
public enum {0}
|
||||
{{
|
||||
None,
|
||||
{1}
|
||||
}}
|
||||
}}
|
||||
";
|
||||
const string TEMPLATE = @"// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
//
|
||||
// This file is automatically generated. DO NOT edit it manually.
|
||||
// Edit the relevant XML or CSV files, and run LocUtil.
|
||||
// A Batch file is provided in the repository root for easy regeneration of the strings tables.
|
||||
|
||||
namespace OpenLiveWriter.Localization
|
||||
{{
|
||||
public enum {0}
|
||||
{{
|
||||
None,
|
||||
{1}
|
||||
}}
|
||||
}}
|
||||
";
|
||||
|
||||
ArrayList commandList = commandIds.ToArrayList();
|
||||
commandList.Sort(new CaseInsensitiveComparer(CultureInfo.InvariantCulture));
|
||||
|
@ -362,22 +389,19 @@ namespace LocUtil
|
|||
index++;
|
||||
}
|
||||
|
||||
sw.Write(string.Format(CultureInfo.InvariantCulture, TEMPLATE, enumName, StringHelper.Join(pairs.ToArray(), ",\r\n\t\t")));
|
||||
sw.Write(string.Format(CultureInfo.InvariantCulture, TEMPLATE, enumName, StringHelper.Join(pairs.ToArray(), ",\r\n ")));
|
||||
}
|
||||
else if (values == null)
|
||||
{
|
||||
const string DESC_TEMPLATE = @"/// <summary>
|
||||
/// {0}
|
||||
/// </summary>
|
||||
{1}";
|
||||
const string DESC_TEMPLATE = "/// <summary>\n /// {0}\n /// </summary>\n {1}";
|
||||
ArrayList descs = new ArrayList();
|
||||
foreach (string command in commandList.ToArray())
|
||||
{
|
||||
string description = ((Values)descriptions[command]).Val as string;
|
||||
description = description.Replace("\n", "\n\t\t/// ");
|
||||
description = description.Replace("\n", "\n /// ").Replace("/// \r\n", "///\r\n");
|
||||
descs.Add(string.Format(CultureInfo.InvariantCulture, DESC_TEMPLATE, description, command));
|
||||
}
|
||||
sw.Write(string.Format(CultureInfo.InvariantCulture, TEMPLATE, enumName, StringHelper.Join(descs.ToArray(), ",\r\n\t\t")));
|
||||
sw.Write(string.Format(CultureInfo.InvariantCulture, TEMPLATE, enumName, StringHelper.Join(descs.ToArray(), ",\r\n ")));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -680,4 +704,14 @@ namespace LocUtil
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class Utf8StringWriter : StringWriter
|
||||
{
|
||||
public Utf8StringWriter(StringBuilder sb) : base(sb)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override Encoding Encoding => Encoding.UTF8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace OpenLiveWriter.Api
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create content using the contents of a LiveClipboad Xml document. Plugin classes which override
|
||||
/// Create content using the contents of a LiveClipboard Xml document. Plugin classes which override
|
||||
/// this method must also be declared with the LiveClipboardContentSourceAttribute.
|
||||
/// </summary>
|
||||
/// <param name="dialogOwner">Owner for any dialogs shown.</param>
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace OpenLiveWriter.Api
|
|||
|
||||
/// <summary>
|
||||
/// Indicates that a a candidate screen capture is available. This event
|
||||
/// allows subscribers to examine the screen captue bitmap in order to determine
|
||||
/// allows subscribers to examine the screen capture bitmap in order to determine
|
||||
/// whether the page is fully loaded and ready for capture.
|
||||
/// </summary>
|
||||
public event HtmlScreenCaptureAvailableHandler HtmlScreenCaptureAvailable;
|
||||
|
|
|
@ -17,7 +17,7 @@ namespace OpenLiveWriter.Api
|
|||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// The feature is suported.
|
||||
/// The feature is supported.
|
||||
/// </summary>
|
||||
Yes,
|
||||
|
||||
|
|
|
@ -157,7 +157,7 @@ namespace OpenLiveWriter.Api
|
|||
/// Add an image to the list of supporting files.
|
||||
/// </summary>
|
||||
/// <param name="fileName">Name of supporting file.</param>
|
||||
/// <param name="image">Souce image</param>
|
||||
/// <param name="image">Source image</param>
|
||||
/// <param name="imageFormat">Source image format</param>
|
||||
void AddImage(string fileName, Image image, ImageFormat imageFormat);
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace OpenLiveWriter.Api
|
|||
/// <summary>
|
||||
/// Initialize a new instance of an InsertableContentSourceAttribute.
|
||||
/// </summary>
|
||||
/// <param name="menuText">Text used to describe the insertable contnet on the Insert menu.</param>
|
||||
/// <param name="menuText">Text used to describe the insertable content on the Insert menu.</param>
|
||||
public InsertableContentSourceAttribute(string menuText)
|
||||
{
|
||||
MenuText = menuText;
|
||||
|
@ -23,7 +23,7 @@ namespace OpenLiveWriter.Api
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Text used to describe the insertable contnet on the Insert menu.
|
||||
/// Text used to describe the insertable content on the Insert menu.
|
||||
/// </summary>
|
||||
public string MenuText
|
||||
{
|
||||
|
|
|
@ -88,7 +88,7 @@ namespace OpenLiveWriter.Api
|
|||
|
||||
/// <summary>
|
||||
/// End-user presentable description of the data format handled by this ContentSource.
|
||||
/// (used within the Live Clipbaord Preferences panel (Optional).
|
||||
/// (used within the Live Clipboard Preferences panel (Optional).
|
||||
/// </summary>
|
||||
public string Description
|
||||
{
|
||||
|
@ -109,7 +109,7 @@ namespace OpenLiveWriter.Api
|
|||
/// <summary>
|
||||
/// Content sub-type handled by this content source. (corresponds to the
|
||||
/// type attribute of the <lc:format> tag). Optional (required only
|
||||
/// for formats which require additional disambiguration of the contentType
|
||||
/// for formats which require additional disambiguation of the contentType
|
||||
/// attribute).
|
||||
/// </summary>
|
||||
public string Type
|
||||
|
|
|
@ -44,7 +44,7 @@ namespace OpenLiveWriter.Api
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Log an error that has occured (writes to the Open Live Writer log file
|
||||
/// Log an error that has occurred (writes to the Open Live Writer log file
|
||||
/// (located at C:\Documents and Settings\%USER%\Application Data\Open Live Writer).
|
||||
/// </summary>
|
||||
/// <param name="message">Error message to log.</param>
|
||||
|
|
|
@ -26,7 +26,7 @@ namespace OpenLiveWriter.Api
|
|||
BypassCache,
|
||||
|
||||
/// <summary>
|
||||
/// Read from the Internet cache if possible, otherwise retreive
|
||||
/// Read from the Internet cache if possible, otherwise retrieve
|
||||
/// from the network. If the request is successful then write the
|
||||
/// response to the Internet cache.
|
||||
/// </summary>
|
||||
|
@ -34,12 +34,12 @@ namespace OpenLiveWriter.Api
|
|||
|
||||
/// <summary>
|
||||
/// Attempt to read the requested resource from the Internet cache
|
||||
/// (in no case attempt to retreive the resource from the network).
|
||||
/// (in no case attempt to retrieve the resource from the network).
|
||||
/// </summary>
|
||||
CacheOnly,
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to retreive the requested resource from the network. If
|
||||
/// Attempt to retrieve the requested resource from the network. If
|
||||
/// the request is successful then write the response to the Internet cache.
|
||||
/// </summary>
|
||||
Reload
|
||||
|
@ -143,7 +143,7 @@ namespace OpenLiveWriter.Api
|
|||
private byte[] _postData;
|
||||
|
||||
/// <summary>
|
||||
/// Retreive the resource (with no timeout).
|
||||
/// Retrieve the resource (with no timeout).
|
||||
/// </summary>
|
||||
/// <returns>A stream representing the requested resource. Can return null
|
||||
/// if the CacheLevel is CacheOnly and the resource could not be found
|
||||
|
|
|
@ -13,7 +13,7 @@ namespace OpenLiveWriter.Api
|
|||
/// <para>Sidebar editor for SmartContent</para>
|
||||
/// <para>There is a single instance of a given SmartContentEditor created for each Open Live Writer
|
||||
/// post editor window. The implementation of SmartContentEditor objects must therefore be
|
||||
/// stateless and assume that they will be the editor for mutliple distince SmartContent objects.</para>
|
||||
/// stateless and assume that they will be the editor for multiple distince SmartContent objects.</para>
|
||||
/// </summary>
|
||||
public class SmartContentEditor : UserControl
|
||||
{
|
||||
|
|
|
@ -37,7 +37,7 @@ namespace OpenLiveWriter.Api
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create content using the contents of a LiveClipboad Xml document. Plugin classes which override
|
||||
/// Create content using the contents of a LiveClipboard Xml document. Plugin classes which override
|
||||
/// this method must also be declared with the LiveClipboardContentSourceAttribute.
|
||||
/// </summary>
|
||||
/// <param name="dialogOwner">Owner for any dialogs shown.</param>
|
||||
|
@ -90,7 +90,7 @@ namespace OpenLiveWriter.Api
|
|||
/// <summary>
|
||||
/// Create a new SmartContentEditor for this ContentSource. The SmartContentEditor is the control
|
||||
/// that appears in the Sidebar whenever a SmartContent object created by this content source
|
||||
/// is selected within the PostEditor. This method must be overriden by all subclasses of SmartContentSource.
|
||||
/// is selected within the PostEditor. This method must be overridden by all subclasses of SmartContentSource.
|
||||
/// </summary>
|
||||
/// <param name="editorSite">Interface to the SmartContentEditor's site.</param>
|
||||
/// <returns>A new instance of a class derived from SmartContentEditor.</returns>
|
||||
|
@ -132,7 +132,7 @@ namespace OpenLiveWriter.Api
|
|||
|
||||
/// <summary>
|
||||
/// Notification that the sizing of an object is complete. The implementation of
|
||||
/// this method should update the ISmartContent object as approriate based on the
|
||||
/// this method should update the ISmartContent object as appropriate based on the
|
||||
/// new size. The editor will first call this method and then call the GenerateEditorHtml
|
||||
/// method to update the display based on the new size.
|
||||
/// </summary>
|
||||
|
@ -156,14 +156,14 @@ namespace OpenLiveWriter.Api
|
|||
|
||||
/// <summary>
|
||||
/// SmartContentSource is resizable (size grippers will appear when the object is selected within the editor).
|
||||
/// If this flag is specified as part of ResizeCapabilties then the OnResizeComplete method should also be
|
||||
/// If this flag is specified as part of ResizeCapabilities then the OnResizeComplete method should also be
|
||||
/// overridden to update the ISmartContent as necessary with the new size of the SmartContent object.
|
||||
/// </summary>
|
||||
Resizable = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Preserve the aspect ratio of the object during resizing. The default aspect ratio to be enforced is the
|
||||
/// ratio of the object prior to resizing. If the desired aspect ratio is staticly known it is highly recommended
|
||||
/// ratio of the object prior to resizing. If the desired aspect ratio is statically known it is highly recommended
|
||||
/// that this ratio be specified within an override of the OnResizeStart method (will eliminate the problem
|
||||
/// of "creeping" change to the aspect ratios with continued resizing).
|
||||
/// </summary>
|
||||
|
@ -171,7 +171,7 @@ namespace OpenLiveWriter.Api
|
|||
|
||||
/// <summary>
|
||||
/// Update the appearance of the smart content object in realtime as the user resizes the object. If this
|
||||
/// flag is specified then the OnResizing method should be overriden to update the state of the ISmartContent
|
||||
/// flag is specified then the OnResizing method should be overridden to update the state of the ISmartContent
|
||||
/// object as resizing occurs. The editor will first call this method and then call the GenerateEditorHtml
|
||||
/// method to update the display as the user resizes.
|
||||
/// </summary>
|
||||
|
@ -199,8 +199,8 @@ namespace OpenLiveWriter.Api
|
|||
private string _resizableElementId = null;
|
||||
|
||||
/// <summary>
|
||||
/// Aspect ratio to be enforced if the ResizeCapabilties.PreserveAspectRatio flag is specified. If the
|
||||
/// desired aspect ratio is staticly known it is highly recommended that this ratio be specified within
|
||||
/// Aspect ratio to be enforced if the ResizeCapabilities.PreserveAspectRatio flag is specified. If the
|
||||
/// desired aspect ratio is statically known it is highly recommended that this ratio be specified within
|
||||
/// the OnResizeStart method (will eliminate the problem of "creeping" change to the aspect ratios with continued resizing).
|
||||
/// </summary>
|
||||
public double AspectRatio
|
||||
|
|
|
@ -9,7 +9,7 @@ using Microsoft.Win32;
|
|||
namespace OpenLiveWriter.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides the abilty to launch the Writer application either to create a new post, open an existing post, or Blog This
|
||||
/// Provides the ability to launch the Writer application either to create a new post, open an existing post, or Blog This
|
||||
/// for a Link, Snippet, Image, or Feed Item.
|
||||
/// </summary>
|
||||
public sealed class WriterApplication
|
||||
|
|
|
@ -124,7 +124,7 @@ namespace OpenLiveWriter.Api
|
|||
private string _description = String.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// URL of the publisher for the Plugin (linked to from Plugins Preferneces panel). Optional.
|
||||
/// URL of the publisher for the Plugin (linked to from Plugins Preferences panel). Optional.
|
||||
/// </summary>
|
||||
public string PublisherUrl
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace OpenLiveWriter.ApplicationFramework.ApplicationStyles
|
|||
private Container components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new insance of the ApplicationStyleSienna class.
|
||||
/// Initializes a new instance of the ApplicationStyleSienna class.
|
||||
/// </summary>
|
||||
public ApplicationStyleLavender()
|
||||
{
|
||||
|
|
|
@ -15,7 +15,7 @@ namespace OpenLiveWriter.ApplicationFramework.ApplicationStyles
|
|||
private Container components = null;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new insance of the ApplicationStyleSienna class.
|
||||
/// Initializes a new instance of the ApplicationStyleSienna class.
|
||||
/// </summary>
|
||||
public ApplicationStyleSienna()
|
||||
{
|
||||
|
|
|
@ -1004,7 +1004,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Set the text.
|
||||
commandBarButtonText = value;
|
||||
|
||||
// Fire the CommandBarButtonTextChangd event.
|
||||
// Fire the CommandBarButtonTextChanged event.
|
||||
OnCommandBarButtonTextChanged(EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
@ -1056,7 +1056,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// set the value
|
||||
commandBarButtonBitmapEnabled = value;
|
||||
|
||||
// since other command bar states can be auto-derivied from enabled we
|
||||
// since other command bar states can be auto-derived from enabled we
|
||||
// need to null them out so they can be updated
|
||||
commandBarButtonBitmapSelected = null;
|
||||
commandBarButtonBitmapPushed = null;
|
||||
|
|
|
@ -1064,7 +1064,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
{
|
||||
if (Command != null)
|
||||
{
|
||||
// calcualte point to show context menu at as well as alternative point
|
||||
// calculate point to show context menu at as well as alternative point
|
||||
// in the case where the menu might go off the right edge of the screen
|
||||
Point menuLocation = VirtualClientPointToScreen(new Point(0, VirtualHeight));
|
||||
int alternativeLocation = VirtualClientPointToScreen(new Point(VirtualWidth, VirtualHeight)).X;
|
||||
|
|
|
@ -8,7 +8,7 @@ using OpenLiveWriter.Controls;
|
|||
namespace OpenLiveWriter.ApplicationFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Command bar control entry. Allows any control to be added to a command bar defintion.
|
||||
/// Command bar control entry. Allows any control to be added to a command bar definition.
|
||||
/// </summary>
|
||||
[
|
||||
DesignTimeVisible(false),
|
||||
|
|
|
@ -68,10 +68,10 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Run the context menu.
|
||||
Command command = commandContextMenu.ShowModal(parentWindow, position, alternateXPosition);
|
||||
|
||||
// Restore our parent window's contetx menu.
|
||||
// Restore our parent window's context menu.
|
||||
parentWindow.ContextMenu = parentContextMenu;
|
||||
|
||||
// Dipose of the context menu.
|
||||
// Dispose of the context menu.
|
||||
commandContextMenu.Dispose();
|
||||
|
||||
// Return the selected command.
|
||||
|
@ -120,10 +120,10 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Run the context menu.
|
||||
Command command = commandContextMenu.ShowModal(parentWindow, position, alternateXPosition);
|
||||
|
||||
// Restore our parent window's contetx menu.
|
||||
// Restore our parent window's context menu.
|
||||
parentWindow.ContextMenu = parentContextMenu;
|
||||
|
||||
// Dipose of the context menu.
|
||||
// Dispose of the context menu.
|
||||
commandContextMenu.Dispose();
|
||||
|
||||
// Return the selected command.
|
||||
|
@ -159,10 +159,10 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Run the context menu.
|
||||
Command command = commandContextMenu.ShowModal(parentWindow, position, alternateXPosition);
|
||||
|
||||
// Restore our parent window's contetx menu.
|
||||
// Restore our parent window's context menu.
|
||||
parentWindow.ContextMenu = parentContextMenu;
|
||||
|
||||
// Dipose of the context menu.
|
||||
// Dispose of the context menu.
|
||||
commandContextMenu.Dispose();
|
||||
|
||||
// Return the selected command.
|
||||
|
|
|
@ -18,7 +18,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
{
|
||||
/* NOTE: When being shown in the context of the browser (or any non .NET
|
||||
* application) this form will not handle any dialog level keyboard
|
||||
* commands (tab, enter, escape, alt-mnenonics, etc.). This is because
|
||||
* commands (tab, enter, escape, alt-mnemonics, etc.). This is because
|
||||
* it is a modeless form that does not have its own thread/message-loop.
|
||||
* Because the form was created by our .NET code the main IE frame that
|
||||
* has the message loop has no idea it needs to route keyboard events'
|
||||
|
@ -131,7 +131,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// (we do this because if actually call Close right here it
|
||||
// will prevent the mouse event that resulted in the deactivation
|
||||
// of the form from actually triggering in the new target
|
||||
// winodw -- this allows the mouse event to trigger and the
|
||||
// window -- this allows the mouse event to trigger and the
|
||||
// form to go away almost instantly
|
||||
Timer closeDelayTimer = new Timer();
|
||||
closeDelayTimer.Tick += new EventHandler(closeDelayTimer_Tick);
|
||||
|
@ -158,7 +158,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// handle painting
|
||||
protected override void OnPaint(PaintEventArgs e)
|
||||
{
|
||||
// get refrence to graphics context
|
||||
// get reference to graphics context
|
||||
Graphics g = e.Graphics;
|
||||
|
||||
// fill background
|
||||
|
@ -273,7 +273,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
/// </summary>
|
||||
private readonly Bitmap miniFormBevelBitmap = ResourceHelper.LoadAssemblyResourceBitmap("Images.CommandBar.MiniFormBevel.png");
|
||||
|
||||
// layout and drawing contants
|
||||
// layout and drawing constants
|
||||
private const int HEADER_INSET = 2;
|
||||
private const int HEADER_HEIGHT = 17;
|
||||
private const int HORIZONTAL_INSET = 10;
|
||||
|
|
|
@ -109,7 +109,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
|
||||
/// <summary>
|
||||
/// The cross-referenced set of active Shortcuts. Keyed by Command.Shortcut. This table
|
||||
/// is not contructed or maintained until it is first used.
|
||||
/// is not constructed or maintained until it is first used.
|
||||
/// </summary>
|
||||
private Hashtable commandShortcutTable = null;
|
||||
|
||||
|
@ -127,14 +127,14 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
|
||||
/// <summary>
|
||||
/// The cross-referenced set of active AcceleratorMnemonic values. Keyed by Command.AcceleratorMnemonic.
|
||||
/// This table is not contructed or maintained until it is first used.
|
||||
/// This table is not constructed or maintained until it is first used.
|
||||
/// </summary>
|
||||
private Hashtable acceleratorMnemonicTable = null;
|
||||
|
||||
/// <summary>
|
||||
/// The cross-referenced set of active CommandBarButtonContextMenuAcceleratorMnemonic
|
||||
/// values. Keyed by Command.CommandBarButtonContextMenuAcceleratorMnemonic. This table is
|
||||
/// not contructed or maintained until it is first used.
|
||||
/// not constructed or maintained until it is first used.
|
||||
/// </summary>
|
||||
private Hashtable commandBarButtonContextMenuAcceleratorMnemonicTable = null;
|
||||
|
||||
|
|
|
@ -58,7 +58,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
private void InitializeComponent()
|
||||
{
|
||||
//
|
||||
// NoValidHyperlinkSpecifiedDisplayMesssage
|
||||
// NoValidHyperlinkSpecifiedDisplayMessage
|
||||
//
|
||||
this.Text = "You must specify a valid hyperlink.";
|
||||
this.Title = "Hyperlink Not Specified";
|
||||
|
|
|
@ -85,7 +85,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// add the command to our internal list
|
||||
_commands.Add(command);
|
||||
|
||||
// add the command to the system command manaager
|
||||
// add the command to the system command manager
|
||||
Context.CommandManager.Add(command);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace Project31.ApplicationFramework
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Menu mergers and aquisitions.
|
||||
/// Menu mergers and acquisitions.
|
||||
/// </summary>
|
||||
internal class MergeMenu
|
||||
{
|
||||
|
|
|
@ -337,7 +337,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
Size textSize = MeasureMenuItemText(e.Graphics, MenuText());
|
||||
|
||||
// Determine the size of the shortcut. If this item does not show a shortcut,
|
||||
// measure a defauly shortcut so it aligns with other menu entries.
|
||||
// measure a default shortcut so it aligns with other menu entries.
|
||||
Size shortcutSize;
|
||||
if (ShowShortcut && Shortcut != Shortcut.None)
|
||||
shortcutSize = MeasureShortcutMenuItemText(e.Graphics, FormatShortcutString(Shortcut));
|
||||
|
@ -528,7 +528,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Fill the menu item with the system-defined menu color.
|
||||
g.FillRectangle(SystemBrushes.Menu, bounds);
|
||||
|
||||
// Fill the bitmap area with the system-defind control color.
|
||||
// Fill the bitmap area with the system-defined control color.
|
||||
Rectangle bitmapAreaRectangle = new Rectangle(bounds.X,
|
||||
bounds.Y,
|
||||
STANDARD_BITMAP_AREA_WIDTH,
|
||||
|
@ -561,7 +561,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Calculate the text area rectangle. This area excludes an area at the right
|
||||
// edge of the menu item where the system draws the cascade indicator. It would
|
||||
// have been better if MenuItem let us draw the indicator (we did say "OwnerDraw"
|
||||
// afterall), but this is just how it works.
|
||||
// after all), but this is just how it works.
|
||||
Rectangle textAreaRectangle = new Rectangle(bounds.X + STANDARD_BITMAP_AREA_WIDTH + STANDARD_TEXT_PADDING,
|
||||
bounds.Y,
|
||||
bounds.Width - (STANDARD_BITMAP_AREA_WIDTH + STANDARD_TEXT_PADDING + STANDARD_RIGHT_EDGE_PAD),
|
||||
|
|
|
@ -81,7 +81,7 @@ namespace OpenLiveWriter.ApplicationFramework.Preferences
|
|||
/// Initializes a new instance of the Preferences class (optionally enable change monitoring)
|
||||
/// </summary>
|
||||
/// <param name="subKey">sub-key name</param>
|
||||
/// <param name="monitorChanges">specifies whether the creator intendes to monitor
|
||||
/// <param name="monitorChanges">specifies whether the creator intends to monitor
|
||||
/// this prefs object for changes by calling the CheckForChanges method</param>
|
||||
public Preferences(string subKey, bool monitorChanges)
|
||||
{
|
||||
|
|
|
@ -33,7 +33,7 @@ namespace OpenLiveWriter.ApplicationFramework.Preferences
|
|||
/// <summary>
|
||||
/// The PreferencesPanel list.
|
||||
/// </summary>
|
||||
private ArrayList preferencesPanelList = new ArrayList();
|
||||
protected ArrayList preferencesPanelList = new ArrayList();
|
||||
|
||||
/// <summary>
|
||||
/// A value which indicates whether the form is initialized.
|
||||
|
@ -391,7 +391,7 @@ namespace OpenLiveWriter.ApplicationFramework.Preferences
|
|||
/// Helper method to save Preferences.
|
||||
/// Returns true if saved successfully.
|
||||
/// </summary>
|
||||
private bool SavePreferences()
|
||||
protected virtual bool SavePreferences()
|
||||
{
|
||||
TabSwitcher tabSwitcher = new TabSwitcher(sideBarControl);
|
||||
|
||||
|
|
|
@ -204,7 +204,7 @@ namespace OpenLiveWriter.ApplicationFramework.Preferences
|
|||
/// <param name="e">An EventArgs that contains the event data.</param>
|
||||
private void preferences_PreferencesModified(object sender, EventArgs e)
|
||||
{
|
||||
// Raise the Modified evebt.
|
||||
// Raise the Modified event.
|
||||
OnModified(EventArgs.Empty);
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
|
||||
public static Bitmap GetGalleryItemImageFromCommand(CommandManager commandManager, CommandId commandId)
|
||||
{
|
||||
// @RIBBON TODO: Deal with high constrast appropriately
|
||||
// @RIBBON TODO: Deal with high contrast appropriately
|
||||
Command command = commandManager.Get(commandId);
|
||||
if (command != null)
|
||||
return command.LargeImage;
|
||||
|
|
|
@ -263,7 +263,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
}
|
||||
}
|
||||
|
||||
// overrideable methods used to customize the UI of the satellite form
|
||||
// overridable methods used to customize the UI of the satellite form
|
||||
//protected virtual CommandBarDefinition FirstCommandBarDefinition { get { return null; } }
|
||||
|
||||
protected virtual Control CreateMainControl() { return null; }
|
||||
|
@ -276,7 +276,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// get{ return _commandBarControl; }
|
||||
//}
|
||||
|
||||
// overrieable processing methods
|
||||
// overridable processing methods
|
||||
protected virtual void OnBackgroundTimerTick() { }
|
||||
|
||||
// override to let base know if the main menu is visible
|
||||
|
|
|
@ -170,7 +170,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
BitmapButton bitmapButton = new BitmapButton();
|
||||
bitmapButton.Tag = index;
|
||||
bitmapButton.Click += new EventHandler(bitmapButton_Click);
|
||||
bitmapButton.AutoSizeHeight = true;
|
||||
bitmapButton.AutoSizeHeight = false;
|
||||
bitmapButton.AutoSizeWidth = false;
|
||||
bitmapButton.ButtonStyle = ButtonStyle.Flat;
|
||||
bitmapButton.TextAlignment = TextAlignment.Right;
|
||||
|
@ -178,7 +178,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
bitmapButton.BitmapEnabled = bitmap;
|
||||
bitmapButton.BitmapSelected = bitmap;
|
||||
bitmapButton.ClickSetsFocus = true;
|
||||
bitmapButton.Size = new Size(Control.Width - (PAD * 2), 0);
|
||||
bitmapButton.Size = new Size(Control.Width - (PAD * 2), 52);
|
||||
bitmapButton.TabStop = false;
|
||||
bitmapButton.AccessibleName = text;
|
||||
bitmapButton.Name = name;
|
||||
|
@ -216,18 +216,24 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
foreach (BitmapButton button in bitmapButtonList)
|
||||
{
|
||||
button.AutoSizeWidth = true;
|
||||
button.AutoSizeHeight = true;
|
||||
// HACK: AutoSizeWidth doesn't quite work right; it doesn't
|
||||
// take effect until SetBoundsCore gets called, so I have
|
||||
// to "change" the width to force the SetBoundsCore call.
|
||||
// Yuck!!
|
||||
button.Width = button.Width + 1;
|
||||
button.Height = button.Height + 1;
|
||||
maxWidth = Math.Max(maxWidth, button.Width);
|
||||
}
|
||||
|
||||
foreach (BitmapButton button in bitmapButtonList)
|
||||
{
|
||||
button.AutoSizeWidth = false;
|
||||
button.AutoSizeHeight = false;
|
||||
button.Width = maxWidth;
|
||||
button.Height =
|
||||
(int)Math.Ceiling(button.Height / (DisplayHelper.PixelsPerLogicalInchY / 96f))
|
||||
+ (button.BitmapEnabled == null ? DisplayHelper.ScaleYCeil(10) : 0); // Add a 10 pixel vertical padding when text-only
|
||||
}
|
||||
|
||||
Width = maxWidth + PAD * 2;
|
||||
|
|
|
@ -417,7 +417,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
|
||||
#region Private Event Handlers
|
||||
/// <summary>
|
||||
/// Propogates the mouse event from the attached control.
|
||||
/// Propagates the mouse event from the attached control.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
|
@ -427,7 +427,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propogates the mouse event from the attached control.
|
||||
/// Propagates the mouse event from the attached control.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
|
@ -437,7 +437,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propogates the mouse event from the attached control.
|
||||
/// Propagates the mouse event from the attached control.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
|
@ -447,7 +447,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propogates the mouse event from the attached control.
|
||||
/// Propagates the mouse event from the attached control.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
|
@ -457,7 +457,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Propogates the mouse event from the attached control.
|
||||
/// Propagates the mouse event from the attached control.
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="e"></param>
|
||||
|
|
|
@ -68,12 +68,12 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
private bool drawSideAndBottomTabPageBorders = true;
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether the tab selecter area is scrollable.
|
||||
/// A value indicating whether the tab selector area is scrollable.
|
||||
/// </summary>
|
||||
private bool scrollableTabSelectorArea = false;
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating whether the tab selecter area will allow tab text/bitmaps to be clipped.
|
||||
/// A value indicating whether the tab selector area will allow tab text/bitmaps to be clipped.
|
||||
/// </summary>
|
||||
private bool allowTabClipping = false;
|
||||
|
||||
|
@ -297,7 +297,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the tab selecter area is scrollable.
|
||||
/// Gets or sets a value indicating whether the tab selector area is scrollable.
|
||||
/// </summary>
|
||||
[
|
||||
Category("Appearance"),
|
||||
|
@ -324,7 +324,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the tab selecter area will allow tab text/bitmaps to be clipped.
|
||||
/// Gets or sets whether the tab selector area will allow tab text/bitmaps to be clipped.
|
||||
/// If false, text or bitmaps will be dropped to shrink the tab size.
|
||||
/// </summary>
|
||||
[
|
||||
|
@ -749,7 +749,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
x += previousTabEntry.IsSelected ? -PAD + 1 : -1;
|
||||
int y = Math.Max(0, tabSelectorAreaSize.Height - tabEntry.TabSelectorLightweightControl.VirtualBounds.Height);
|
||||
|
||||
// Latout the tab entry.
|
||||
// Layout the tab entry.
|
||||
tabEntry.TabSelectorLightweightControl.VirtualLocation = new Point(x - tabScrollerPosition, y);
|
||||
|
||||
// Adjust the x offset to account for the tab entry.
|
||||
|
|
|
@ -8,8 +8,8 @@ using System.Windows.Forms;
|
|||
namespace OpenLiveWriter.ApplicationFramework
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper control for TabLightwieightControl. The TabPageContainer provides the
|
||||
/// container control below the tabs and is the parent of the TabPage controls of the TabLightwieightControl
|
||||
/// Helper control for TabLightweightControl. The TabPageContainer provides the
|
||||
/// container control below the tabs and is the parent of the TabPage controls of the TabLightweightControl
|
||||
/// are shown/hidden.
|
||||
/// </summary>
|
||||
public class TabPageContainerControl : UserControl
|
||||
|
|
|
@ -27,7 +27,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
private Timer timerAutoScroll;
|
||||
|
||||
/// <summary>
|
||||
/// A value indicating wheter auto-scroll occurred. This value is used to suppress the
|
||||
/// A value indicating whether auto-scroll occurred. This value is used to suppress the
|
||||
/// Scroll event, which we do not want to fire if we auto-scrolled.
|
||||
/// </summary>
|
||||
private bool autoScrollOccurred = false;
|
||||
|
@ -206,7 +206,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
1,
|
||||
faceRectangle.Height - 1);
|
||||
|
||||
// Draw the botom edge.
|
||||
// Draw the bottom edge.
|
||||
e.Graphics.FillRectangle(solidBrush,
|
||||
faceRectangle.X,
|
||||
faceRectangle.Bottom - 1,
|
||||
|
|
|
@ -675,7 +675,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Make the command bar visible before getting height.
|
||||
FirstCommandBarLightweightControl.Visible = true;
|
||||
|
||||
// Obtaing the height and lay it out.
|
||||
// Obtain the height and lay it out.
|
||||
int firstCommandBarHeight = FirstCommandBarLightweightControl.DefaultVirtualSize.Height;
|
||||
FirstCommandBarLightweightControl.VirtualBounds = new Rectangle(0, commandBarAreaHeight, Width, firstCommandBarHeight);
|
||||
|
||||
|
@ -700,7 +700,7 @@ namespace OpenLiveWriter.ApplicationFramework
|
|||
// Make the command bar visible before getting height.
|
||||
SecondCommandBarLightweightControl.Visible = true;
|
||||
|
||||
// Obtaing the height and lay it out.
|
||||
// Obtain the height and lay it out.
|
||||
int secondCommandBarHeight = SecondCommandBarLightweightControl.DefaultVirtualSize.Height;
|
||||
SecondCommandBarLightweightControl.VirtualBounds = new Rectangle(0, commandBarAreaHeight, Width, secondCommandBarHeight);
|
||||
|
||||
|
|
|
@ -169,5 +169,11 @@ namespace OpenLiveWriter.BlogClient
|
|||
/// </summary>
|
||||
/// <param name="tc"></param>
|
||||
protected abstract void VerifyCredentials(TransientCredentials tc);
|
||||
|
||||
/// <summary>
|
||||
/// Almost all blogs supported are remote blogs, with few exceptions.
|
||||
/// eg. local static sites
|
||||
/// </summary>
|
||||
public virtual bool RemoteDetectionPossible { get; } = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ namespace OpenLiveWriter.BlogClient
|
|||
/// <summary>
|
||||
/// Class used to install and remove (on dispose) the UI context for the currently
|
||||
/// running thread. To enforce the idiom of install/remove this is the ONLY
|
||||
/// suppported mechanism for manipulating the UI context. This class should be
|
||||
/// supported mechanism for manipulating the UI context. This class should be
|
||||
/// instantiated under a "using" statement on every thread that will call
|
||||
/// BlogClient code. Further, every time a new dialog box which may call blog
|
||||
/// client code (e.g. OpenPost, UpdateTemplate, etc.) should also construct
|
||||
|
|
|
@ -175,6 +175,13 @@ namespace OpenLiveWriter.BlogClient
|
|||
}
|
||||
private const string IS_GOOGLE_BLOGGER_BLOG = "IsGoogleBloggerBlog";
|
||||
|
||||
public bool IsStaticSiteBlog
|
||||
{
|
||||
get { return Settings.GetBoolean(IS_STATIC_SITE_BLOG, false); }
|
||||
set { Settings.SetBoolean(IS_STATIC_SITE_BLOG, value); }
|
||||
}
|
||||
private const string IS_STATIC_SITE_BLOG = "IsStaticSiteBlog";
|
||||
|
||||
/// <summary>
|
||||
/// Id of the weblog on the host service
|
||||
/// </summary>
|
||||
|
|
|
@ -134,6 +134,8 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
AddClientType(typeof(BloggerAtomClient));
|
||||
AddClientType(typeof(SharePointClient));
|
||||
AddClientType(typeof(WordPressClient));
|
||||
AddClientType(typeof(TistoryBlogClient));
|
||||
AddClientType(typeof(StaticSite.StaticSiteClient));
|
||||
}
|
||||
return _clientTypes;
|
||||
}
|
||||
|
|
|
@ -460,7 +460,8 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
const string GPHOTO_NS_URI = "http://schemas.google.com/photos/2007";
|
||||
|
||||
//TransientCredentials transientCredentials = Credentials.TransientCredentials as TransientCredentials;
|
||||
Uri picasaUri = new Uri("http://picasaweb.google.com/data/feed/api/user/default");
|
||||
// TODO: HACK: The deprecation-extension flag keeps the deprecated Picasa API alive.
|
||||
Uri picasaUri = new Uri("https://picasaweb.google.com/data/feed/api/user/default?deprecation-extension=true");
|
||||
|
||||
try
|
||||
{
|
||||
|
|
|
@ -6,14 +6,18 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using System.Xml;
|
||||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using OpenLiveWriter.Localization;
|
||||
using Google.Apis.Auth.OAuth2;
|
||||
using Google.Apis.Blogger.v3;
|
||||
using Google.Apis.Drive.v3;
|
||||
using GoogleDriveData = Google.Apis.Drive.v3.Data;
|
||||
using Google.Apis.Util.Store;
|
||||
using Google.Apis.Services;
|
||||
using Google.Apis.Auth.OAuth2.Flows;
|
||||
|
@ -33,8 +37,11 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
public class GoogleBloggerv3Client : BlogClientBase, IBlogClient
|
||||
{
|
||||
// These URLs map to OAuth2 permission scopes for Google Blogger.
|
||||
public static string PicasaServiceScope = "https://picasaweb.google.com/data";
|
||||
public static string BloggerServiceScope = BloggerService.Scope.Blogger;
|
||||
public static readonly string[] GoogleAPIScopes =
|
||||
{
|
||||
DriveService.Scope.DriveFile,
|
||||
BloggerService.Scope.Blogger
|
||||
};
|
||||
public static char LabelDelimiter = ',';
|
||||
|
||||
/// <summary>
|
||||
|
@ -48,7 +55,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
// browser window and prompt the user for permissions and then write those permissions to the IDataStore.
|
||||
return GoogleWebAuthorizationBroker.AuthorizeAsync(
|
||||
GoogleClientSecrets.Load(ClientSecretsStream).Secrets,
|
||||
new List<string>() { BloggerServiceScope, PicasaServiceScope },
|
||||
GoogleAPIScopes,
|
||||
blogId,
|
||||
taskCancellationToken,
|
||||
GetCredentialsDataStoreForBlog(blogId));
|
||||
|
@ -205,6 +212,16 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
});
|
||||
}
|
||||
|
||||
private DriveService GetDriveService()
|
||||
{
|
||||
TransientCredentials transientCredentials = Login();
|
||||
return new DriveService(new BaseClientService.Initializer()
|
||||
{
|
||||
HttpClientInitializer = (UserCredential)transientCredentials.Token,
|
||||
ApplicationName = string.Format(CultureInfo.InvariantCulture, "{0} {1}", ApplicationEnvironment.ProductName, ApplicationEnvironment.ProductVersion),
|
||||
});
|
||||
}
|
||||
|
||||
private bool IsValidToken(TokenResponse token)
|
||||
{
|
||||
// If the token is expired but we have a non-null RefreshToken, we can assume the token will be
|
||||
|
@ -244,7 +261,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
{
|
||||
ClientSecretsStream = ClientSecretsStream,
|
||||
DataStore = GetCredentialsDataStoreForBlog(tc.Username),
|
||||
Scopes = new List<string>() { BloggerServiceScope, PicasaServiceScope },
|
||||
Scopes = GoogleAPIScopes,
|
||||
});
|
||||
|
||||
var loadTokenTask = flow.LoadTokenAsync(tc.Username, CancellationToken.None);
|
||||
|
@ -613,92 +630,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
albumName = StringHelper.Reverse(chunks[1]);
|
||||
}
|
||||
|
||||
string EDIT_MEDIA_LINK = "EditMediaLink";
|
||||
string srcUrl;
|
||||
string editUri = uploadContext.Settings.GetString(EDIT_MEDIA_LINK, null);
|
||||
if (editUri == null || editUri.Length == 0)
|
||||
{
|
||||
PostNewImage(albumName, path, uploadContext.BlogId, out srcUrl, out editUri);
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
UpdateImage(editUri, path, out srcUrl, out editUri);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Trace.Fail(e.ToString());
|
||||
if (e is WebException)
|
||||
HttpRequestHelper.LogException((WebException)e);
|
||||
|
||||
bool success = false;
|
||||
srcUrl = null; // compiler complains without this line
|
||||
try
|
||||
{
|
||||
// couldn't update existing image? try posting a new one
|
||||
PostNewImage(albumName, path, uploadContext.BlogId, out srcUrl, out editUri);
|
||||
success = true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
if (!success)
|
||||
throw; // rethrow the exception from the update, not the post
|
||||
}
|
||||
}
|
||||
uploadContext.Settings.SetString(EDIT_MEDIA_LINK, editUri);
|
||||
|
||||
PicasaRefererBlockingWorkaround(uploadContext.BlogId, uploadContext.Role, ref srcUrl);
|
||||
|
||||
return srcUrl;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// "It looks like the problem with the inline image is due to referrer checking.
|
||||
/// The thumbnail image being used is protected for display only on certain domains.
|
||||
/// These domains include *.blogspot.com and *.google.com. This user is using a
|
||||
/// feature in Blogger which allows him to display his blog directly on his own
|
||||
/// domain, which will not pass the referrer checking.
|
||||
///
|
||||
/// "The maximum size of a thumbnail image that can be displayed on non-*.blogspot.com
|
||||
/// domains is 800px. (blogs don't actually appear at *.google.com). However, if you
|
||||
/// request a 800px thumbnail, and the image is less than 800px for the maximum
|
||||
/// dimension, then the original image will be returned without the referrer
|
||||
/// restrictions. That sounds like it will work for you, so feel free to give it a
|
||||
/// shot and let me know if you have any further questions or problems."
|
||||
/// -- Anonymous Google Employee
|
||||
/// </summary>
|
||||
private void PicasaRefererBlockingWorkaround(string blogId, FileUploadRole role, ref string srcUrl)
|
||||
{
|
||||
if (role == FileUploadRole.LinkedImage && Options.UsePicasaS1600h)
|
||||
{
|
||||
try
|
||||
{
|
||||
int lastSlash = srcUrl.LastIndexOf('/');
|
||||
string srcUrl2 = srcUrl.Substring(0, lastSlash)
|
||||
+ "/s1600-h"
|
||||
+ srcUrl.Substring(lastSlash);
|
||||
HttpWebRequest req = HttpRequestHelper.CreateHttpWebRequest(srcUrl2, true);
|
||||
req.Method = "HEAD";
|
||||
req.GetResponse().Close();
|
||||
srcUrl = srcUrl2;
|
||||
return;
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
Debug.Fail("Picasa s1600-h hack failed: " + we.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
srcUrl += ((srcUrl.IndexOf('?') >= 0) ? "&" : "?") + "imgmax=800";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Trace.Fail("Unexpected error while doing Picasa upload: " + ex.ToString());
|
||||
}
|
||||
return PostNewImage(albumName, path);
|
||||
}
|
||||
|
||||
public void DoAfterPublishUploadWork(IFileUploadContext uploadContext)
|
||||
|
@ -726,283 +658,77 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
#region Picasa image uploading - stolen from BloggerAtomClient
|
||||
#region Google Drive image uploading, heavily adapted from Picasa image uploading - stolen from BloggerAtomClient
|
||||
|
||||
public string GetBlogImagesAlbum(string albumName, string blogId)
|
||||
private List<GoogleDriveData.File> GetAllFolders(DriveService drive)
|
||||
{
|
||||
const string FEED_REL = "http://schemas.google.com/g/2005#feed";
|
||||
// Navigate GDrive pagination and return a list of all the user's top level folders
|
||||
var folders = new List<GoogleDriveData.File>();
|
||||
GoogleDriveData.FileList fileList;
|
||||
string pageToken = null;
|
||||
do
|
||||
{
|
||||
var listRequest = drive.Files.List();
|
||||
listRequest.Q = "mimeType='application/vnd.google-apps.folder'";
|
||||
fileList = listRequest.Execute();
|
||||
|
||||
if (fileList.Files != null) foreach (var folder in fileList.Files) folders.Add(folder);
|
||||
pageToken = fileList.NextPageToken;
|
||||
} while (pageToken != null);
|
||||
return folders;
|
||||
}
|
||||
|
||||
private GoogleDriveData.File GetBlogImagesFolder(DriveService drive, string folderName)
|
||||
{
|
||||
// Get the ID of the Google Drive 'Open Live Writer' folder, creating it if it doesn't exist
|
||||
var matchingFolders = GetAllFolders(drive).Where(folder => folder.Name == folderName);
|
||||
if (matchingFolders.Count() > 0) return matchingFolders.First();
|
||||
|
||||
// Attempt to create and return the folder as it does not exist
|
||||
return drive.Files.Create(new GoogleDriveData.File()
|
||||
{
|
||||
Name = folderName,
|
||||
MimeType = "application/vnd.google-apps.folder"
|
||||
}).Execute();
|
||||
}
|
||||
|
||||
private string PostNewImage(string imagesFolderName, string filename)
|
||||
{
|
||||
var drive = GetDriveService();
|
||||
var imagesFolder = GetBlogImagesFolder(drive, imagesFolderName);
|
||||
FilesResource.CreateMediaUpload uploadReq;
|
||||
|
||||
// Create a FileStream for the image to upload
|
||||
using (var imageFileStream = new System.IO.FileStream(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read)) {
|
||||
// Detect mime type for file based on extension
|
||||
var imageMime = MimeMapping.GetMimeMapping(filename);
|
||||
// Upload the image to the images folder, naming it with a GUID to prevent clashes
|
||||
uploadReq = drive.Files.Create(new GoogleDriveData.File()
|
||||
{
|
||||
Name = Guid.NewGuid().ToString(),
|
||||
Parents = new string[] { imagesFolder.Id },
|
||||
OriginalFilename = Path.GetFileName(filename)
|
||||
}, imageFileStream, imageMime);
|
||||
uploadReq.Fields = "id,webContentLink"; // Retrieve Id and WebContentLink fields
|
||||
var uploadRes = uploadReq.Upload();
|
||||
if (uploadRes.Status != Google.Apis.Upload.UploadStatus.Completed)
|
||||
throw new BlogClientFileTransferException(
|
||||
String.Format(Res.Get(StringId.BCEFileTransferTransferringFile), Path.GetFileName(filename)),
|
||||
"BloggerDriveError",
|
||||
$"Google Drive image upload for {Path.GetFileName(filename)} failed.\nDetails: {uploadRes.Exception}");
|
||||
}
|
||||
|
||||
// Make the uploaded file public
|
||||
var imageFile = uploadReq.ResponseBody;
|
||||
drive.Permissions.Create(new GoogleDriveData.Permission()
|
||||
{
|
||||
Type = "anyone",
|
||||
Role = "reader"
|
||||
}, imageFile.Id).Execute();
|
||||
|
||||
Uri picasaUri = new Uri("https://picasaweb.google.com/data/feed/api/user/default");
|
||||
|
||||
var picasaId = string.Empty;
|
||||
|
||||
try
|
||||
{
|
||||
Uri reqUri = picasaUri;
|
||||
XmlDocument albumListDoc = AtomClient.xmlRestRequestHelper.Get(ref reqUri, CreateAuthorizationFilter(), "kind", "album");
|
||||
var idNode = albumListDoc.SelectSingleNode(@"/atom:feed/gphoto:user", _nsMgr) as XmlElement;
|
||||
if (idNode != null)
|
||||
{
|
||||
var id = AtomProtocolVersion.V10DraftBlogger.TextNodeToPlaintext(idNode);
|
||||
picasaId = id;
|
||||
}
|
||||
|
||||
foreach (XmlElement entryEl in albumListDoc.SelectNodes(@"/atom:feed/atom:entry", _nsMgr))
|
||||
{
|
||||
XmlElement titleNode = entryEl.SelectSingleNode(@"atom:title", _nsMgr) as XmlElement;
|
||||
if (titleNode != null)
|
||||
{
|
||||
string titleText = AtomProtocolVersion.V10DraftBlogger.TextNodeToPlaintext(titleNode);
|
||||
if (titleText == albumName)
|
||||
{
|
||||
XmlNode numPhotosRemainingNode = entryEl.SelectSingleNode("gphoto:numphotosremaining/text()", _nsMgr);
|
||||
if (numPhotosRemainingNode != null)
|
||||
{
|
||||
int numPhotosRemaining;
|
||||
if (int.TryParse(numPhotosRemainingNode.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out numPhotosRemaining))
|
||||
{
|
||||
if (numPhotosRemaining < 1)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
string selfHref = AtomEntry.GetLink(entryEl, _nsMgr, FEED_REL, "application/atom+xml", null, reqUri);
|
||||
if (selfHref.Length > 1)
|
||||
return selfHref;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
HttpWebResponse httpWebResponse = we.Response as HttpWebResponse;
|
||||
if (httpWebResponse != null)
|
||||
{
|
||||
HttpRequestHelper.DumpResponse(httpWebResponse);
|
||||
if (httpWebResponse.StatusCode == HttpStatusCode.NotFound)
|
||||
{
|
||||
throw new BlogClientOperationCancelledException();
|
||||
}
|
||||
}
|
||||
throw;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
XmlDocument newDoc = new XmlDocument();
|
||||
XmlElement newEntryEl = newDoc.CreateElement("atom", "entry", AtomProtocolVersion.V10DraftBlogger.NamespaceUri);
|
||||
newDoc.AppendChild(newEntryEl);
|
||||
|
||||
XmlElement newTitleEl = newDoc.CreateElement("atom", "title", AtomProtocolVersion.V10DraftBlogger.NamespaceUri);
|
||||
newTitleEl.SetAttribute("type", "text");
|
||||
newTitleEl.InnerText = albumName;
|
||||
newEntryEl.AppendChild(newTitleEl);
|
||||
|
||||
XmlElement newSummaryEl = newDoc.CreateElement("atom", "summary", AtomProtocolVersion.V10DraftBlogger.NamespaceUri);
|
||||
newSummaryEl.SetAttribute("type", "text");
|
||||
newSummaryEl.InnerText = Res.Get(StringId.BloggerImageAlbumDescription);
|
||||
newEntryEl.AppendChild(newSummaryEl);
|
||||
|
||||
XmlElement newAccessEl = newDoc.CreateElement("gphoto", "access", photoNS.Uri);
|
||||
newAccessEl.InnerText = "private";
|
||||
newEntryEl.AppendChild(newAccessEl);
|
||||
|
||||
XmlElement newCategoryEl = newDoc.CreateElement("atom", "category", AtomProtocolVersion.V10DraftBlogger.NamespaceUri);
|
||||
newCategoryEl.SetAttribute("scheme", "http://schemas.google.com/g/2005#kind");
|
||||
newCategoryEl.SetAttribute("term", "http://schemas.google.com/photos/2007#album");
|
||||
newEntryEl.AppendChild(newCategoryEl);
|
||||
|
||||
Uri postUri = picasaUri;
|
||||
XmlDocument newAlbumResult = AtomClient.xmlRestRequestHelper.Post(ref postUri, CreateAuthorizationFilter(), "application/atom+xml", newDoc, null);
|
||||
XmlElement newAlbumResultEntryEl = newAlbumResult.SelectSingleNode("/atom:entry", _nsMgr) as XmlElement;
|
||||
Debug.Assert(newAlbumResultEntryEl != null);
|
||||
return AtomEntry.GetLink(newAlbumResultEntryEl, _nsMgr, FEED_REL, "application/atom+xml", null, postUri);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
// Ignore
|
||||
}
|
||||
|
||||
// If we've got this far, it means creating the Open Live Writer album has failed.
|
||||
// We will now try and use the Blogger assigned folder.
|
||||
if (!string.IsNullOrEmpty(picasaId))
|
||||
{
|
||||
var service = GetService();
|
||||
|
||||
var userInfo = service.BlogUserInfos.Get("self", blogId).Execute();
|
||||
|
||||
// If the PhotosAlbumKey is "0", this means the user has never posted to Blogger from the
|
||||
// Blogger web interface, which means the album has never been created and so there's nothing
|
||||
// for us to use.
|
||||
if (userInfo.BlogUserInfoValue.PhotosAlbumKey != "0")
|
||||
{
|
||||
var bloggerPicasaUrl = $"https://picasaweb.google.com/data/feed/api/user/{picasaId}/albumid/{userInfo.BlogUserInfoValue.PhotosAlbumKey}";
|
||||
return bloggerPicasaUrl;
|
||||
}
|
||||
}
|
||||
|
||||
// If we've got this far, it means the user is going to have to manually create their own album.
|
||||
throw new BlogClientFileTransferException("Unable to upload to Blogger", "BloggerError", "We were unable to create a folder for your images, please go to http://openlivewriter.org/tutorials/googlePhotoFix.html to see how to do this");
|
||||
// Retrieve the appropiate URL for inlining the image, splitting off the download parameter
|
||||
return imageFile.WebContentLink.Split('&').First();
|
||||
}
|
||||
|
||||
private void ShowPicasaSignupPrompt(object sender, EventArgs e)
|
||||
{
|
||||
if (DisplayMessage.Show(MessageId.PicasawebSignup) == DialogResult.Yes)
|
||||
{
|
||||
ShellHelper.LaunchUrl("http://picasaweb.google.com");
|
||||
}
|
||||
}
|
||||
|
||||
private void PostNewImage(string albumName, string filename, string blogId, out string srcUrl, out string editUri)
|
||||
{
|
||||
for (int retry = 0; retry < MaxRetries; retry++)
|
||||
{
|
||||
var transientCredentials = Login();
|
||||
try
|
||||
{
|
||||
string albumUrl = GetBlogImagesAlbum(albumName, blogId);
|
||||
HttpWebResponse response = RedirectHelper.GetResponse(albumUrl, new RedirectHelper.RequestFactory(new UploadFileRequestFactory(this, filename, "POST").Create));
|
||||
using (Stream s = response.GetResponseStream())
|
||||
{
|
||||
ParseMediaEntry(s, out srcUrl, out editUri);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
if (retry < MaxRetries - 1 &&
|
||||
we.Response as HttpWebResponse != null &&
|
||||
((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
// HTTP 403 Forbidden means our OAuth access token is not valid.
|
||||
RefreshAccessToken(transientCredentials);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Trace.Fail("Should never get here");
|
||||
throw new ApplicationException("Should never get here");
|
||||
}
|
||||
|
||||
private void UpdateImage(string editUri, string filename, out string srcUrl, out string newEditUri)
|
||||
{
|
||||
for (int retry = 0; retry < MaxRetries; retry++)
|
||||
{
|
||||
var transientCredentials = Login();
|
||||
HttpWebResponse response;
|
||||
bool conflict = false;
|
||||
try
|
||||
{
|
||||
response = RedirectHelper.GetResponse(editUri, new RedirectHelper.RequestFactory(new UploadFileRequestFactory(this, filename, "PUT").Create));
|
||||
}
|
||||
catch (WebException we)
|
||||
{
|
||||
if (retry < MaxRetries - 1 &&
|
||||
we.Response as HttpWebResponse != null)
|
||||
{
|
||||
if (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Conflict)
|
||||
{
|
||||
response = (HttpWebResponse)we.Response;
|
||||
conflict = true;
|
||||
}
|
||||
else if (((HttpWebResponse)we.Response).StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
// HTTP 403 Forbidden means our OAuth access token is not valid.
|
||||
RefreshAccessToken(transientCredentials);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
using (Stream s = response.GetResponseStream())
|
||||
{
|
||||
ParseMediaEntry(s, out srcUrl, out newEditUri);
|
||||
}
|
||||
|
||||
if (!conflict)
|
||||
{
|
||||
return; // success!
|
||||
}
|
||||
|
||||
editUri = newEditUri;
|
||||
}
|
||||
|
||||
Trace.Fail("Should never get here");
|
||||
throw new ApplicationException("Should never get here");
|
||||
}
|
||||
|
||||
private void ParseMediaEntry(Stream s, out string srcUrl, out string editUri)
|
||||
{
|
||||
srcUrl = null;
|
||||
|
||||
// First try <content src>
|
||||
XmlDocument xmlDoc = new XmlDocument();
|
||||
xmlDoc.Load(s);
|
||||
XmlElement contentEl = xmlDoc.SelectSingleNode("/atom:entry/atom:content", _nsMgr) as XmlElement;
|
||||
if (contentEl != null)
|
||||
srcUrl = XmlHelper.GetUrl(contentEl, "@src", _nsMgr, null);
|
||||
|
||||
// Then try media RSS
|
||||
if (srcUrl == null || srcUrl.Length == 0)
|
||||
{
|
||||
contentEl = xmlDoc.SelectSingleNode("/atom:entry/media:group/media:content[@medium='image']", _nsMgr) as XmlElement;
|
||||
if (contentEl == null)
|
||||
throw new ArgumentException("Picasa photo entry was missing content element");
|
||||
srcUrl = XmlHelper.GetUrl(contentEl, "@url", _nsMgr, null);
|
||||
}
|
||||
|
||||
editUri = AtomEntry.GetLink(xmlDoc.SelectSingleNode("/atom:entry", _nsMgr) as XmlElement, _nsMgr, "edit-media", null, null, null);
|
||||
}
|
||||
|
||||
private class UploadFileRequestFactory
|
||||
{
|
||||
private readonly GoogleBloggerv3Client _parent;
|
||||
private readonly string _filename;
|
||||
private readonly string _method;
|
||||
|
||||
public UploadFileRequestFactory(GoogleBloggerv3Client parent, string filename, string method)
|
||||
{
|
||||
_parent = parent;
|
||||
_filename = filename;
|
||||
_method = method;
|
||||
}
|
||||
|
||||
public HttpWebRequest Create(string uri)
|
||||
{
|
||||
// TODO: choose rational timeout values
|
||||
HttpWebRequest request = HttpRequestHelper.CreateHttpWebRequest(uri, false);
|
||||
|
||||
_parent.CreateAuthorizationFilter().Invoke(request);
|
||||
|
||||
request.ContentType = MimeHelper.GetContentType(Path.GetExtension(_filename));
|
||||
try
|
||||
{
|
||||
request.Headers.Add("Slug", Path.GetFileNameWithoutExtension(_filename));
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
request.Headers.Add("Slug", "Image");
|
||||
}
|
||||
|
||||
request.Method = _method;
|
||||
|
||||
using (Stream s = request.GetRequestStream())
|
||||
{
|
||||
using (Stream inS = new FileStream(_filename, FileMode.Open, FileAccess.Read, FileShare.Read))
|
||||
{
|
||||
StreamHelper.Transfer(inS, s);
|
||||
}
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class Category
|
||||
|
|
|
@ -192,7 +192,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
|
||||
// If this chunk starts with a <p> tag--either because
|
||||
// it always did (like <p class="foo">, which doesn't get
|
||||
// dropped either by WordPress or by our regexes above), or
|
||||
// dropped either by WordPress or by our regexs above), or
|
||||
// because we added one just now--we want to end it with
|
||||
// a </p> if necessary.
|
||||
if (Regex.IsMatch(chunk, @"<p(?:\s|>)") && !Regex.IsMatch(chunk, @"</p>"))
|
||||
|
|
|
@ -180,7 +180,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
|
||||
// Some weblog providers (such as Drupal) actually return the mt category struct for
|
||||
// metaWeblog.getCategories. In this case the parsing above would have failed to
|
||||
// extract either a name or an id. Parse out the values here if necesssary
|
||||
// extract either a name or an id. Parse out the values here if necessary
|
||||
|
||||
// populate the name field if we haven't gotten it another way
|
||||
if (catName == null)
|
||||
|
@ -273,7 +273,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
return false;
|
||||
|
||||
// we support inline category addition and we don't require a special
|
||||
// api for heirarchical categories (inline api can't handle parent specification)
|
||||
// api for hierarchical categories (inline api can't handle parent specification)
|
||||
if (Options.SupportsNewCategoriesInline && !Options.SupportsHierarchicalCategories)
|
||||
return false;
|
||||
|
||||
|
|
|
@ -199,7 +199,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
return attachSettings.AttachmentUrl;
|
||||
}
|
||||
|
||||
//SharePoint blogIDs are formatted: webguid#listguid
|
||||
//SharePoint blogIDs are formatted: webguid#listGuid
|
||||
public static string SharepointBlogIdToListGuid(string blogId)
|
||||
{
|
||||
int listGuidIndex = blogId.IndexOf("#", StringComparison.OrdinalIgnoreCase);
|
||||
|
|
|
@ -0,0 +1,593 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml;
|
||||
|
||||
using OpenLiveWriter.Api;
|
||||
using OpenLiveWriter.BlogClient.Providers;
|
||||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using OpenLiveWriter.Localization;
|
||||
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
[BlogClient(StaticSiteClient.CLIENT_TYPE, StaticSiteClient.CLIENT_TYPE)]
|
||||
public class StaticSiteClient : BlogClientBase, IBlogClient
|
||||
{
|
||||
// The 'provider' concept doesn't really apply to local static sites
|
||||
// Store these required constants here so they're in one place
|
||||
public const string PROVIDER_ID = "D0E0062F-7540-4462-94FD-DC55004D95E6";
|
||||
public const string SERVICE_NAME = "Static Site Generator";
|
||||
public const string POST_API_URL = "http://localhost/"; // A valid URI is required for BlogClientManager to instantiate a URI object on.
|
||||
public const string CLIENT_TYPE = "StaticSite";
|
||||
|
||||
public static Regex WEB_UNSAFE_CHARS = new Regex("[^A-Za-z0-9- ]*");
|
||||
|
||||
public IBlogClientOptions Options { get; private set; }
|
||||
|
||||
private StaticSiteConfig Config;
|
||||
|
||||
public StaticSiteClient(Uri postApiUrl, IBlogCredentialsAccessor credentials)
|
||||
: base(credentials)
|
||||
{
|
||||
Config = StaticSiteConfig.LoadConfigFromCredentials(credentials);
|
||||
|
||||
// Set the client options
|
||||
var options = new BlogClientOptions();
|
||||
ConfigureClientOptions(options);
|
||||
Options = options;
|
||||
}
|
||||
|
||||
protected override void VerifyCredentials(TransientCredentials transientCredentials)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void OverrideOptions(IBlogClientOptions newClientOptions)
|
||||
{
|
||||
Options = newClientOptions;
|
||||
}
|
||||
|
||||
public BlogInfo[] GetUsersBlogs() => new BlogInfo[0];
|
||||
|
||||
public BlogPostCategory[] GetCategories(string blogId) =>
|
||||
StaticSitePost.GetAllPosts(Config, false)
|
||||
.SelectMany(post => post.BlogPost.Categories.Select(cat => cat.Name))
|
||||
.Distinct()
|
||||
.Select(cat => new BlogPostCategory(cat))
|
||||
.ToArray();
|
||||
|
||||
public BlogPostKeyword[] GetKeywords(string blogId) => new BlogPostKeyword[0];
|
||||
|
||||
/// <summary>
|
||||
/// Returns recent posts
|
||||
/// </summary>
|
||||
/// <param name="blogId"></param>
|
||||
/// <param name="maxPosts"></param>
|
||||
/// <param name="includeCategories"></param>
|
||||
/// <param name="now">If null, then includes future posts. If non-null, then only includes posts before the *UTC* 'now' time.</param>
|
||||
/// <returns></returns>
|
||||
public BlogPost[] GetRecentPosts(string blogId, int maxPosts, bool includeCategories, DateTime? now) =>
|
||||
StaticSitePost.GetAllPosts(Config, true)
|
||||
.Select(post => post.BlogPost)
|
||||
.Where(post => post != null && (now == null || post.DatePublished < now))
|
||||
.OrderByDescending(post => post.DatePublished)
|
||||
.Take(maxPosts)
|
||||
.ToArray();
|
||||
|
||||
public string NewPost(string blogId, BlogPost post, INewCategoryContext newCategoryContext, bool publish, out string etag, out XmlDocument remotePost)
|
||||
{
|
||||
if(!publish && !Options.SupportsPostAsDraft)
|
||||
{
|
||||
Trace.Fail("Static site does not support drafts, cannot post.");
|
||||
throw new BlogClientPostAsDraftUnsupportedException();
|
||||
}
|
||||
remotePost = null;
|
||||
etag = "";
|
||||
|
||||
// Create a StaticSitePost on the provided post
|
||||
return DoNewItem(new StaticSitePost(Config, post, !publish));
|
||||
}
|
||||
|
||||
public bool EditPost(string blogId, BlogPost post, INewCategoryContext newCategoryContext, bool publish, out string etag, out XmlDocument remotePost)
|
||||
{
|
||||
if (!publish && !Options.SupportsPostAsDraft)
|
||||
{
|
||||
Trace.Fail("Static site does not support drafts, cannot post.");
|
||||
throw new BlogClientPostAsDraftUnsupportedException();
|
||||
}
|
||||
remotePost = null;
|
||||
etag = "";
|
||||
|
||||
// Create a StaticSitePost on the provided post
|
||||
var ssgPost = new StaticSitePost(Config, post, !publish);
|
||||
|
||||
if(ssgPost.FilePathById == null)
|
||||
{
|
||||
// If we are publishing and there exists a draft with this ID, delete it.
|
||||
if (publish)
|
||||
{
|
||||
var filePath = new StaticSitePost(Config, post, true).FilePathById;
|
||||
if (filePath != null) File.Delete(filePath);
|
||||
}
|
||||
|
||||
// Existing post could not be found to edit, call NewPost instead;
|
||||
NewPost(blogId, post, newCategoryContext, publish, out etag, out remotePost);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set slug to existing slug on post
|
||||
ssgPost.Slug = post.Slug;
|
||||
|
||||
return DoEditItem(ssgPost);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to get a post with the specified id (note: may return null
|
||||
/// if the post could not be found on the remote server)
|
||||
/// </summary>
|
||||
public BlogPost GetPost(string blogId, string postId)
|
||||
=> StaticSitePost.GetPostById(Config, postId).BlogPost;
|
||||
|
||||
public void DeletePost(string blogId, string postId, bool publish)
|
||||
{
|
||||
var post = StaticSitePost.GetPostById(Config, postId);
|
||||
if (post == null) throw new BlogClientException(
|
||||
Res.Get(StringId.SSGErrorPostDoesNotExistTitle),
|
||||
Res.Get(StringId.SSGErrorPostDoesNotExistText));
|
||||
DoDeleteItem(post);
|
||||
}
|
||||
|
||||
public BlogPost GetPage(string blogId, string pageId)
|
||||
{
|
||||
var page = StaticSitePage.GetPageById(Config, pageId);
|
||||
page.ResolveParent();
|
||||
return page.BlogPost;
|
||||
}
|
||||
|
||||
public PageInfo[] GetPageList(string blogId) =>
|
||||
StaticSitePage.GetAllPages(Config).Select(page => page.PageInfo).ToArray();
|
||||
|
||||
public BlogPost[] GetPages(string blogId, int maxPages) =>
|
||||
StaticSitePage.GetAllPages(Config)
|
||||
.Select(page => page.BlogPost)
|
||||
.OrderByDescending(page => page.DatePublished)
|
||||
.Take(maxPages)
|
||||
.ToArray();
|
||||
|
||||
public string NewPage(string blogId, BlogPost page, bool publish, out string etag, out XmlDocument remotePost)
|
||||
{
|
||||
if(!publish)
|
||||
{
|
||||
Trace.Fail("Posting pages as drafts not yet implemented.");
|
||||
throw new BlogClientPostAsDraftUnsupportedException();
|
||||
}
|
||||
//if (!publish && !Options.SupportsPostAsDraft)
|
||||
//{
|
||||
// Trace.Fail("Static site does not support drafts, cannot post.");
|
||||
// throw new BlogClientPostAsDraftUnsupportedException();
|
||||
//}
|
||||
|
||||
remotePost = null;
|
||||
etag = "";
|
||||
|
||||
// Create a StaticSitePost on the provided page
|
||||
return DoNewItem(new StaticSitePage(Config, page));
|
||||
}
|
||||
|
||||
public bool EditPage(string blogId, BlogPost page, bool publish, out string etag, out XmlDocument remotePost)
|
||||
{
|
||||
if (!publish)
|
||||
{
|
||||
Trace.Fail("Posting pages as drafts not yet implemented.");
|
||||
throw new BlogClientPostAsDraftUnsupportedException();
|
||||
}
|
||||
//if (!publish && !Options.SupportsPostAsDraft)
|
||||
//{
|
||||
// Trace.Fail("Static site does not support drafts, cannot post.");
|
||||
// throw new BlogClientPostAsDraftUnsupportedException();
|
||||
//}
|
||||
remotePost = null;
|
||||
etag = "";
|
||||
|
||||
// Create a StaticSitePage on the provided page
|
||||
var ssgPage = new StaticSitePage(Config, page);
|
||||
|
||||
if (ssgPage.FilePathById == null)
|
||||
{
|
||||
// Existing page could not be found to edit, call NewPage instead;
|
||||
NewPage(blogId, page, publish, out etag, out remotePost);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Set slug to existing slug on page
|
||||
ssgPage.Slug = page.Slug;
|
||||
|
||||
return DoEditItem(ssgPage);
|
||||
}
|
||||
|
||||
public void DeletePage(string blogId, string pageId)
|
||||
{
|
||||
var page = StaticSitePage.GetPageById(Config, pageId);
|
||||
if (page == null) throw new BlogClientException(
|
||||
Res.Get(StringId.SSGErrorPageDoesNotExistTitle),
|
||||
Res.Get(StringId.SSGErrorPageDoesNotExistText));
|
||||
DoDeleteItem(page);
|
||||
}
|
||||
|
||||
public AuthorInfo[] GetAuthors(string blogId) => throw new NotImplementedException();
|
||||
|
||||
public bool? DoesFileNeedUpload(IFileUploadContext uploadContext) => null;
|
||||
|
||||
public string DoBeforePublishUploadWork(IFileUploadContext uploadContext)
|
||||
{
|
||||
string path = uploadContext.GetContentsLocalFilePath();
|
||||
return DoPostImage(path);
|
||||
}
|
||||
|
||||
public void DoAfterPublishUploadWork(IFileUploadContext uploadContext)
|
||||
{
|
||||
}
|
||||
|
||||
public string AddCategory(string blogId, BlogPostCategory category) =>
|
||||
throw new BlogClientMethodUnsupportedException("AddCategory");
|
||||
|
||||
public BlogPostCategory[] SuggestCategories(string blogId, string partialCategoryName)
|
||||
=> throw new BlogClientMethodUnsupportedException("SuggestCategories");
|
||||
|
||||
/// <summary>
|
||||
/// Currently sends an UNAUTHENTICATED HTTP request.
|
||||
/// If a static site requires authentication, this may be implemented here later.
|
||||
/// </summary>
|
||||
/// <param name="requestUri"></param>
|
||||
/// <param name="timeoutMs"></param>
|
||||
/// <param name="filter"></param>
|
||||
/// <returns></returns>
|
||||
public HttpWebResponse SendAuthenticatedHttpRequest(string requestUri, int timeoutMs, HttpRequestFilter filter)
|
||||
=> BlogClientHelper.SendAuthenticatedHttpRequest(requestUri, filter, (HttpWebRequest request) => {});
|
||||
|
||||
public BlogInfo[] GetImageEndpoints()
|
||||
=> throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Returns if this StaticSiteGeneratorClient is secure
|
||||
/// Returns true for now as we trust the user publish script
|
||||
/// </summary>
|
||||
public bool IsSecure => true;
|
||||
|
||||
/// <summary>
|
||||
/// Remote detection is now possible as SendAuthenticatedHttpRequest has been implemented.
|
||||
/// </summary>
|
||||
public override bool RemoteDetectionPossible => true;
|
||||
|
||||
// Authentication is handled by publish script at the moment
|
||||
protected override bool RequiresPassword => false;
|
||||
|
||||
#region StaticSiteItem generic methods
|
||||
|
||||
/// <summary>
|
||||
/// Generic method to prepare and publish a new StaticSiteItem derived instance
|
||||
/// </summary>
|
||||
/// <param name="item">a new StaticSiteItem derived instance</param>
|
||||
/// <returns>the new StaticSitePost ID</returns>
|
||||
private string DoNewItem(StaticSiteItem item)
|
||||
{
|
||||
// Ensure the post has an ID
|
||||
var newPostId = item.EnsureId();
|
||||
// Ensure the post has a date
|
||||
item.EnsureDatePublished();
|
||||
// Ensure the post has a safe slug
|
||||
item.EnsureSafeSlug();
|
||||
// Save the post to disk under it's new slug-based path
|
||||
item.SaveToFile(item.FilePathBySlug);
|
||||
|
||||
try
|
||||
{
|
||||
// Build the site, if required
|
||||
if (Config.BuildCommand != string.Empty) DoSiteBuild();
|
||||
|
||||
// Publish the site
|
||||
DoSitePublish();
|
||||
|
||||
return newPostId;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Clean up our output file
|
||||
File.Delete(item.FilePathBySlug);
|
||||
// Throw the exception up
|
||||
throw ex;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic method to edit an already-published StaticSiteItem derived instance
|
||||
/// </summary>
|
||||
/// <param name="item">an existing StaticSiteItem derived instance</param>
|
||||
/// <returns>True if successful</returns>
|
||||
private bool DoEditItem(StaticSiteItem item)
|
||||
{
|
||||
// Copy the existing post to a temporary file
|
||||
var backupFileName = Path.GetTempFileName();
|
||||
File.Copy(item.FilePathById, backupFileName, true);
|
||||
|
||||
bool renameOccurred = false;
|
||||
// Store the old file path and slug
|
||||
string oldPath = item.FilePathById;
|
||||
//string oldSlug = item.DiskSlugFromFilePathById;
|
||||
|
||||
try
|
||||
{
|
||||
// Determine if the post file needs renaming (slug, date or parent change)
|
||||
if (item.FilePathById != item.FilePathBySlug)
|
||||
{
|
||||
renameOccurred = true;
|
||||
|
||||
// Find a new safe slug for the post
|
||||
item.Slug = item.FindNewSlug(item.Slug, safe: true);
|
||||
// Remove the old file
|
||||
File.Delete(oldPath);
|
||||
// Save to the new file
|
||||
item.SaveToFile(item.FilePathBySlug);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Save the post to disk based on it's existing id
|
||||
item.SaveToFile(item.FilePathById);
|
||||
}
|
||||
|
||||
// Build the site, if required
|
||||
if (Config.BuildCommand != string.Empty) DoSiteBuild();
|
||||
|
||||
// Publish the site
|
||||
DoSitePublish();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Clean up the failed output
|
||||
if (renameOccurred)
|
||||
{
|
||||
// Delete the rename target
|
||||
File.Delete(item.FilePathBySlug);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Delete the original file
|
||||
File.Delete(item.FilePathById);
|
||||
}
|
||||
|
||||
// Copy the backup to the old location
|
||||
File.Copy(backupFileName, oldPath, overwrite: true);
|
||||
|
||||
// Delete the backup
|
||||
File.Delete(backupFileName);
|
||||
|
||||
// Throw the exception up
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Delete a StaticSiteItem from disk, and publish the changes
|
||||
/// </summary>
|
||||
/// <param name="item">a StaticSiteItem</param>
|
||||
private void DoDeleteItem(StaticSiteItem item)
|
||||
{
|
||||
var backupFileName = Path.GetTempFileName();
|
||||
File.Copy(item.FilePathById, backupFileName, true);
|
||||
|
||||
try
|
||||
{
|
||||
File.Delete(item.FilePathById);
|
||||
|
||||
// Build the site, if required
|
||||
if (Config.BuildCommand != string.Empty) DoSiteBuild();
|
||||
|
||||
// Publish the site
|
||||
DoSitePublish();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
File.Copy(backupFileName, item.FilePathById, overwrite: true);
|
||||
File.Delete(backupFileName);
|
||||
|
||||
// Throw the exception up
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Copy image to images directory, returning the URL on site (eg. http://example.com/images/test.jpg)
|
||||
/// This method does not upload the image, it is assumed this will be done later on.
|
||||
/// </summary>
|
||||
/// <param name="filePath">Path to image on disk</param>
|
||||
/// <returns>URL to image on site</returns>
|
||||
private string DoPostImage(string filePath)
|
||||
{
|
||||
// Generate a unique file name
|
||||
var fileExt = Path.GetExtension(filePath);
|
||||
string uniqueName = "";
|
||||
|
||||
for (int i = 0; i <= 1000; i++)
|
||||
{
|
||||
uniqueName = Path.GetFileNameWithoutExtension(filePath).Replace(" ", "");
|
||||
|
||||
if (i == 1000)
|
||||
{
|
||||
// Failed to find a unique file name, return a GUID
|
||||
uniqueName = Guid.NewGuid().ToString();
|
||||
break;
|
||||
}
|
||||
if (i > 0) uniqueName += $"-{i}";
|
||||
if (!File.Exists(Path.Combine(Config.LocalSitePath, Config.ImagesPath, uniqueName + fileExt ))) break;
|
||||
}
|
||||
|
||||
// Copy the image to the images path
|
||||
File.Copy(filePath, Path.Combine(Config.LocalSitePath, Config.ImagesPath, uniqueName + fileExt));
|
||||
|
||||
// I attempted to return an absolute server path here, however other parts of OLW expect a fully formed URI
|
||||
// This may cause issue for users who decide to relocate their site to a different URL.
|
||||
// I also attempted to strip the protocol here, however C# does not think protocol-less URIs are valid
|
||||
return Path.Combine(Config.SiteUrl, Config.ImagesPath, uniqueName + fileExt).Replace("\\", "/");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build the static site
|
||||
/// </summary>
|
||||
private void DoSiteBuild()
|
||||
{
|
||||
string stdout, stderr;
|
||||
var proc = RunSiteCommand(Config.BuildCommand, out stdout, out stderr);
|
||||
if (proc.ExitCode != 0)
|
||||
{
|
||||
throw new BlogClientException(
|
||||
StringId.SSGBuildErrorTitle,
|
||||
StringId.SSGBuildErrorText,
|
||||
Res.Get(StringId.ProductNameVersioned),
|
||||
proc.ExitCode.ToString(),
|
||||
Config.ShowCmdWindows ? "N/A" : stdout,
|
||||
Config.ShowCmdWindows ? "N/A" : stderr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Publish the static site
|
||||
/// </summary>
|
||||
private void DoSitePublish()
|
||||
{
|
||||
string stdout, stderr;
|
||||
var proc = RunSiteCommand(Config.PublishCommand, out stdout, out stderr);
|
||||
if (proc.ExitCode != 0)
|
||||
{
|
||||
throw new BlogClientException(
|
||||
StringId.SSGPublishErrorTitle,
|
||||
StringId.SSGPublishErrorText,
|
||||
Res.Get(StringId.ProductNameVersioned),
|
||||
proc.ExitCode.ToString(),
|
||||
Config.ShowCmdWindows ? "N/A" : stdout,
|
||||
Config.ShowCmdWindows ? "N/A" : stderr
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Run a command from the site directory
|
||||
/// </summary>
|
||||
/// <param name="localCommand">Command to run, releative to site directory</param>
|
||||
/// <param name="stdout">String which will receive the command stdout</param>
|
||||
/// <param name="stderr">String which will receive the command stderr</param>
|
||||
/// <returns></returns>
|
||||
private Process RunSiteCommand(string localCommand, out string outStdout, out string outStderr)
|
||||
{
|
||||
var proc = new Process();
|
||||
string stdout = "";
|
||||
string stderr = "";
|
||||
|
||||
// If a 32-bit process on a 64-bit system, call the 64-bit cmd
|
||||
proc.StartInfo.FileName = (!Environment.Is64BitProcess && Environment.Is64BitOperatingSystem) ?
|
||||
$"{Environment.GetEnvironmentVariable("windir")}\\Sysnative\\cmd.exe" : // 32-on-64, launch sysnative cmd
|
||||
"cmd.exe"; // Launch regular cmd
|
||||
|
||||
// Set working directory to local site path
|
||||
proc.StartInfo.WorkingDirectory = Config.LocalSitePath;
|
||||
|
||||
proc.StartInfo.RedirectStandardInput = !Config.ShowCmdWindows;
|
||||
proc.StartInfo.RedirectStandardError = !Config.ShowCmdWindows;
|
||||
proc.StartInfo.RedirectStandardOutput = !Config.ShowCmdWindows;
|
||||
proc.StartInfo.CreateNoWindow = !Config.ShowCmdWindows;
|
||||
proc.StartInfo.UseShellExecute = false;
|
||||
|
||||
proc.StartInfo.Arguments = $"/C {localCommand}";
|
||||
|
||||
if(!Config.ShowCmdWindows)
|
||||
{
|
||||
|
||||
proc.OutputDataReceived += new DataReceivedEventHandler((sender, e) =>
|
||||
{
|
||||
if (!String.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
stdout += e.Data;
|
||||
Trace.WriteLine($"StaticSiteClient stdout: {e.Data}");
|
||||
}
|
||||
});
|
||||
|
||||
proc.ErrorDataReceived += new DataReceivedEventHandler((sender, e) =>
|
||||
{
|
||||
if (!String.IsNullOrEmpty(e.Data))
|
||||
{
|
||||
stderr += e.Data;
|
||||
Trace.WriteLine($"StaticSiteClient stderr: {e.Data}");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
proc.Start();
|
||||
if(!Config.ShowCmdWindows)
|
||||
{
|
||||
proc.BeginOutputReadLine();
|
||||
proc.BeginErrorReadLine();
|
||||
}
|
||||
|
||||
if(Config.CmdTimeoutMs < 0)
|
||||
{
|
||||
// If timeout is negative, timeout is disabled.
|
||||
proc.WaitForExit();
|
||||
} else
|
||||
{
|
||||
if (!proc.WaitForExit(Config.CmdTimeoutMs))
|
||||
{
|
||||
// Timeout reached
|
||||
try { proc.Kill(); } catch { } // Attempt to kill the process
|
||||
throw new BlogClientException(
|
||||
Res.Get(StringId.SSGErrorCommandTimeoutTitle),
|
||||
Res.Get(StringId.SSGErrorCommandTimeoutText));
|
||||
}
|
||||
}
|
||||
|
||||
// The caller will have all output waiting in outStdout and outStderr
|
||||
outStdout = stdout;
|
||||
outStderr = stderr;
|
||||
return proc;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the relevant BlogClientOptions for this client based on values from the StaticSiteConfig
|
||||
/// </summary>
|
||||
/// <param name="clientOptions">A BlogClientOptions instance</param>
|
||||
private void ConfigureClientOptions(BlogClientOptions clientOptions)
|
||||
{
|
||||
clientOptions.SupportsPages = clientOptions.SupportsPageParent = Config.PagesEnabled;
|
||||
clientOptions.SupportsPostAsDraft = Config.DraftsEnabled;
|
||||
clientOptions.SupportsFileUpload = Config.ImagesEnabled;
|
||||
clientOptions.SupportsImageUpload = Config.ImagesEnabled ? SupportsFeature.Yes : SupportsFeature.No;
|
||||
clientOptions.SupportsScripts = clientOptions.SupportsEmbeds = SupportsFeature.Yes;
|
||||
clientOptions.SupportsExtendedEntries = true;
|
||||
|
||||
// Blog template is downloaded from publishing a test post
|
||||
clientOptions.SupportsAutoUpdate = true;
|
||||
|
||||
clientOptions.SupportsCategories = true;
|
||||
clientOptions.SupportsMultipleCategories = true;
|
||||
clientOptions.SupportsNewCategories = true;
|
||||
clientOptions.SupportsKeywords = false;
|
||||
|
||||
clientOptions.FuturePublishDateWarning = true;
|
||||
clientOptions.SupportsCustomDate = clientOptions.SupportsCustomDateUpdate = true;
|
||||
clientOptions.SupportsSlug = true;
|
||||
clientOptions.SupportsAuthor = false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,341 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
public class StaticSiteConfig
|
||||
{
|
||||
// The credential keys where the configuration is stored.
|
||||
private const string CONFIG_POSTS_PATH = "PostsPath";
|
||||
private const string CONFIG_PAGES_ENABLED = "PagesEnabled";
|
||||
private const string CONFIG_PAGES_PATH = "PagesPath";
|
||||
private const string CONFIG_DRAFTS_ENABLED = "DraftsEnabled";
|
||||
private const string CONFIG_DRAFTS_PATH = "DraftsPath";
|
||||
private const string CONFIG_IMAGES_ENABLED = "ImagesEnabled";
|
||||
private const string CONFIG_IMAGES_PATH = "ImagesPath";
|
||||
private const string CONFIG_BUILDING_ENABLED = "BuildingEnabled";
|
||||
private const string CONFIG_OUTPUT_PATH = "OutputPath";
|
||||
private const string CONFIG_BUILD_COMMAND = "BuildCommand";
|
||||
private const string CONFIG_PUBLISH_COMMAND = "PublishCommand";
|
||||
private const string CONFIG_SITE_URL = "SiteUrl"; // Store Site Url in credentials as well, for acccess by StaticSiteClient
|
||||
private const string CONFIG_SHOW_CMD_WINDOWS = "ShowCmdWindows";
|
||||
private const string CONFIG_CMD_TIMEOUT_MS = "CmdTimeoutMs";
|
||||
private const string CONFIG_INITIALISED = "Initialised";
|
||||
|
||||
public static int DEFAULT_CMD_TIMEOUT = 60000;
|
||||
|
||||
// Public Site Url is stored in the blog's BlogConfig. Loading is handled in this class, but saving is handled from the WizardController.
|
||||
// This is done to avoid referencing PostEditor from this project.
|
||||
|
||||
// NOTE: When setting default config values below, also make sure to alter LoadFromCredentials to not overwrite defaults if a key was not found.
|
||||
|
||||
/// <summary>
|
||||
/// The full path to the local static site 'project' directory
|
||||
/// </summary>
|
||||
public string LocalSitePath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Path to Posts directory, relative to LocalSitePath
|
||||
/// </summary>
|
||||
public string PostsPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// True if Pages can be posted to this blog.
|
||||
/// </summary>
|
||||
public bool PagesEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Path to Pages directory, relative to LocalSitePath.
|
||||
/// </summary>
|
||||
public string PagesPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// True if Drafts can be saved to this blog.
|
||||
/// </summary>
|
||||
public bool DraftsEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Path to Drafts directory, relative to LocalSitePath.
|
||||
/// </summary>
|
||||
public string DraftsPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// True if Images can be uploaded to this blog.
|
||||
/// </summary>
|
||||
public bool ImagesEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Path to Images directory, relative to LocalSitePath.
|
||||
/// </summary>
|
||||
public string ImagesPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// True if site is locally built.
|
||||
/// </summary>
|
||||
public bool BuildingEnabled { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Path to Output directory, relative to LocalSitePath. Can be possibly used in future for preset publishing routines.
|
||||
/// </summary>
|
||||
public string OutputPath { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Build command, executed by system command interpreter with LocalSitePath working directory
|
||||
/// </summary>
|
||||
public string BuildCommand { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Publish command, executed by system command interpreter with LocalSitePath working directory
|
||||
/// </summary>
|
||||
public string PublishCommand { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Public site URL
|
||||
/// </summary>
|
||||
public string SiteUrl { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Site title
|
||||
/// </summary>
|
||||
public string SiteTitle { get; set; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Show CMD windows. Useful for debugging. Default is false.
|
||||
/// </summary>
|
||||
public bool ShowCmdWindows { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// Timeout for commands. Default is 60k MS (60 seconds).
|
||||
/// </summary>
|
||||
public int CmdTimeoutMs { get; set; } = DEFAULT_CMD_TIMEOUT;
|
||||
|
||||
/// <summary>
|
||||
/// Used to determine if parameter detection has occurred, default false.
|
||||
/// </summary>
|
||||
public bool Initialised { get; set; } = false;
|
||||
|
||||
public StaticSiteConfigFrontMatterKeys FrontMatterKeys { get; set; } = new StaticSiteConfigFrontMatterKeys();
|
||||
|
||||
public StaticSiteConfigValidator Validator => new StaticSiteConfigValidator(this);
|
||||
|
||||
/// <summary>
|
||||
/// Load site configuration from blog credentials
|
||||
/// </summary>
|
||||
/// <param name="creds">An IBlogCredentialsAccessor</param>
|
||||
public void LoadFromCredentials(IBlogCredentialsAccessor creds)
|
||||
{
|
||||
LocalSitePath = creds.Username;
|
||||
PostsPath = creds.GetCustomValue(CONFIG_POSTS_PATH);
|
||||
|
||||
PagesEnabled = creds.GetCustomValue(CONFIG_PAGES_ENABLED) == "1";
|
||||
PagesPath = creds.GetCustomValue(CONFIG_PAGES_PATH);
|
||||
|
||||
DraftsEnabled = creds.GetCustomValue(CONFIG_DRAFTS_ENABLED) == "1";
|
||||
DraftsPath = creds.GetCustomValue(CONFIG_DRAFTS_PATH);
|
||||
|
||||
ImagesEnabled = creds.GetCustomValue(CONFIG_IMAGES_ENABLED) == "1";
|
||||
ImagesPath = creds.GetCustomValue(CONFIG_IMAGES_PATH);
|
||||
|
||||
BuildingEnabled = creds.GetCustomValue(CONFIG_BUILDING_ENABLED) == "1";
|
||||
OutputPath = creds.GetCustomValue(CONFIG_OUTPUT_PATH);
|
||||
BuildCommand = creds.GetCustomValue(CONFIG_BUILD_COMMAND);
|
||||
|
||||
PublishCommand = creds.GetCustomValue(CONFIG_PUBLISH_COMMAND);
|
||||
|
||||
SiteUrl = creds.GetCustomValue(CONFIG_SITE_URL); // This will be overidden in LoadFromBlogSettings, HomepageUrl is considered a more accurate source of truth
|
||||
|
||||
ShowCmdWindows = creds.GetCustomValue(CONFIG_SHOW_CMD_WINDOWS) == "1";
|
||||
if (creds.GetCustomValue(CONFIG_CMD_TIMEOUT_MS) != string.Empty) CmdTimeoutMs = int.Parse(creds.GetCustomValue(CONFIG_CMD_TIMEOUT_MS));
|
||||
Initialised = creds.GetCustomValue(CONFIG_INITIALISED) == "1";
|
||||
|
||||
// Load FrontMatterKeys
|
||||
FrontMatterKeys = StaticSiteConfigFrontMatterKeys.LoadKeysFromCredentials(creds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Loads site configuration from blog settings
|
||||
/// </summary>
|
||||
/// <param name="blogCredentials">An IBlogSettingsAccessor</param>
|
||||
public void LoadFromBlogSettings(IBlogSettingsAccessor blogSettings)
|
||||
{
|
||||
LoadFromCredentials(blogSettings.Credentials);
|
||||
|
||||
SiteUrl = blogSettings.HomepageUrl;
|
||||
SiteTitle = blogSettings.BlogName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Saves site configuration to blog credentials
|
||||
/// </summary>
|
||||
public void SaveToCredentials(IBlogCredentialsAccessor creds)
|
||||
{
|
||||
// Set username to Local Site Path
|
||||
creds.Username = LocalSitePath;
|
||||
creds.SetCustomValue(CONFIG_POSTS_PATH, PostsPath);
|
||||
|
||||
creds.SetCustomValue(CONFIG_PAGES_ENABLED, PagesEnabled ? "1" : "0");
|
||||
creds.SetCustomValue(CONFIG_PAGES_PATH, PagesPath);
|
||||
|
||||
creds.SetCustomValue(CONFIG_DRAFTS_ENABLED, DraftsEnabled ? "1" : "0");
|
||||
creds.SetCustomValue(CONFIG_DRAFTS_PATH, DraftsPath);
|
||||
|
||||
creds.SetCustomValue(CONFIG_IMAGES_ENABLED, ImagesEnabled ? "1" : "0");
|
||||
creds.SetCustomValue(CONFIG_IMAGES_PATH, ImagesPath);
|
||||
|
||||
creds.SetCustomValue(CONFIG_BUILDING_ENABLED, BuildingEnabled ? "1" : "0");
|
||||
creds.SetCustomValue(CONFIG_OUTPUT_PATH, OutputPath);
|
||||
creds.SetCustomValue(CONFIG_BUILD_COMMAND, BuildCommand);
|
||||
|
||||
creds.SetCustomValue(CONFIG_PUBLISH_COMMAND, PublishCommand);
|
||||
creds.SetCustomValue(CONFIG_SITE_URL, SiteUrl);
|
||||
|
||||
creds.SetCustomValue(CONFIG_SHOW_CMD_WINDOWS, ShowCmdWindows ? "1" : "0");
|
||||
creds.SetCustomValue(CONFIG_CMD_TIMEOUT_MS, CmdTimeoutMs.ToString());
|
||||
creds.SetCustomValue(CONFIG_INITIALISED, Initialised ? "1" : "0");
|
||||
|
||||
// Save FrontMatterKeys
|
||||
FrontMatterKeys.SaveToCredentials(creds);
|
||||
}
|
||||
|
||||
public void SaveToCredentials(IBlogCredentials blogCredentials)
|
||||
=> SaveToCredentials(new BlogCredentialsAccessor("", blogCredentials));
|
||||
|
||||
public StaticSiteConfig Clone()
|
||||
=> new StaticSiteConfig()
|
||||
{
|
||||
LocalSitePath = LocalSitePath,
|
||||
PostsPath = PostsPath,
|
||||
PagesEnabled = PagesEnabled,
|
||||
PagesPath = PagesPath,
|
||||
DraftsEnabled = DraftsEnabled,
|
||||
DraftsPath = DraftsPath,
|
||||
ImagesEnabled = ImagesEnabled,
|
||||
ImagesPath = ImagesPath,
|
||||
BuildingEnabled = BuildingEnabled,
|
||||
OutputPath = OutputPath,
|
||||
BuildCommand = BuildCommand,
|
||||
PublishCommand = PublishCommand,
|
||||
SiteUrl = SiteUrl,
|
||||
SiteTitle = SiteTitle,
|
||||
ShowCmdWindows = ShowCmdWindows,
|
||||
CmdTimeoutMs = CmdTimeoutMs,
|
||||
Initialised = Initialised,
|
||||
|
||||
FrontMatterKeys = FrontMatterKeys.Clone()
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Create a new StaticSiteConfig instance and load site configuration from blog credentials
|
||||
/// </summary>
|
||||
/// <param name="blogCredentials">An IBlogCredentialsAccessor</param>
|
||||
public static StaticSiteConfig LoadConfigFromCredentials(IBlogCredentialsAccessor blogCredentials)
|
||||
{
|
||||
var config = new StaticSiteConfig();
|
||||
config.LoadFromCredentials(blogCredentials);
|
||||
return config;
|
||||
}
|
||||
|
||||
public static StaticSiteConfig LoadConfigFromCredentials(IBlogCredentials blogCredentials)
|
||||
=> LoadConfigFromCredentials(new BlogCredentialsAccessor("", blogCredentials));
|
||||
|
||||
/// <summary>
|
||||
/// Create a new StaticSiteConfig instance and loads site configuration from blog settings
|
||||
/// </summary>
|
||||
/// <param name="blogCredentials">An IBlogSettingsAccessor</param>
|
||||
public static StaticSiteConfig LoadConfigFromBlogSettings(IBlogSettingsAccessor blogSettings)
|
||||
{
|
||||
var config = new StaticSiteConfig();
|
||||
config.LoadFromBlogSettings(blogSettings);
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents the YAML keys used for each of these properties in the front matter
|
||||
/// </summary>
|
||||
public class StaticSiteConfigFrontMatterKeys
|
||||
{
|
||||
private const string CONFIG_ID_KEY = "FrontMatterKey.Id";
|
||||
private const string CONFIG_TITLE_KEY = "FrontMatterKey.Title";
|
||||
private const string CONFIG_DATE_KEY = "FrontMatterKey.Date";
|
||||
private const string CONFIG_LAYOUT_KEY = "FrontMatterKey.Layout";
|
||||
private const string CONFIG_TAGS_KEY = "FrontMatterKey.Tags";
|
||||
private const string CONFIG_PARENT_ID_KEY = "FrontMatterKey.ParentId";
|
||||
private const string CONFIG_PERMALINK_KEY = "FrontMatterKey.Permalink";
|
||||
|
||||
public enum KeyIdentifier
|
||||
{
|
||||
Id,
|
||||
Title,
|
||||
Date,
|
||||
Layout,
|
||||
Tags,
|
||||
ParentId,
|
||||
Permalink
|
||||
}
|
||||
|
||||
public string IdKey { get; set; } = "id";
|
||||
public string TitleKey { get; set; } = "title";
|
||||
public string DateKey { get; set; } = "date";
|
||||
public string LayoutKey { get; set; } = "layout";
|
||||
public string TagsKey { get; set; } = "tags";
|
||||
public string ParentIdKey { get; set; } = "parent_id";
|
||||
public string PermalinkKey { get; set; } = "permalink";
|
||||
|
||||
public StaticSiteConfigFrontMatterKeys Clone()
|
||||
=> new StaticSiteConfigFrontMatterKeys()
|
||||
{
|
||||
IdKey = IdKey,
|
||||
TitleKey = TitleKey,
|
||||
DateKey = DateKey,
|
||||
LayoutKey = LayoutKey,
|
||||
TagsKey = TagsKey,
|
||||
ParentIdKey = ParentIdKey,
|
||||
PermalinkKey = PermalinkKey
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Load front matter keys configuration from blog credentials
|
||||
/// </summary>
|
||||
/// <param name="creds">An IBlogCredentialsAccessor</param>
|
||||
public void LoadFromCredentials(IBlogCredentialsAccessor creds)
|
||||
{
|
||||
if (creds.GetCustomValue(CONFIG_ID_KEY) != string.Empty) IdKey = creds.GetCustomValue(CONFIG_ID_KEY);
|
||||
if (creds.GetCustomValue(CONFIG_TITLE_KEY) != string.Empty) TitleKey = creds.GetCustomValue(CONFIG_TITLE_KEY);
|
||||
if (creds.GetCustomValue(CONFIG_DATE_KEY) != string.Empty) DateKey = creds.GetCustomValue(CONFIG_DATE_KEY);
|
||||
if (creds.GetCustomValue(CONFIG_LAYOUT_KEY) != string.Empty) LayoutKey = creds.GetCustomValue(CONFIG_LAYOUT_KEY);
|
||||
if (creds.GetCustomValue(CONFIG_TAGS_KEY) != string.Empty) TagsKey = creds.GetCustomValue(CONFIG_TAGS_KEY);
|
||||
if (creds.GetCustomValue(CONFIG_PARENT_ID_KEY) != string.Empty) ParentIdKey = creds.GetCustomValue(CONFIG_PARENT_ID_KEY);
|
||||
if (creds.GetCustomValue(CONFIG_PERMALINK_KEY) != string.Empty) PermalinkKey = creds.GetCustomValue(CONFIG_PERMALINK_KEY);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Save front matter keys configuration to blog credentials
|
||||
/// </summary>
|
||||
/// <param name="creds">An IBlogCredentialsAccessor</param>
|
||||
public void SaveToCredentials(IBlogCredentialsAccessor creds)
|
||||
{
|
||||
creds.SetCustomValue(CONFIG_ID_KEY, IdKey);
|
||||
creds.SetCustomValue(CONFIG_TITLE_KEY, TitleKey);
|
||||
creds.SetCustomValue(CONFIG_DATE_KEY, DateKey);
|
||||
creds.SetCustomValue(CONFIG_LAYOUT_KEY, LayoutKey);
|
||||
creds.SetCustomValue(CONFIG_TAGS_KEY, TagsKey);
|
||||
creds.SetCustomValue(CONFIG_PARENT_ID_KEY, ParentIdKey);
|
||||
creds.SetCustomValue(CONFIG_PERMALINK_KEY, PermalinkKey);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new StaticSiteConfigFrontMatterKeys instance and load configuration from blog credentials
|
||||
/// </summary>
|
||||
/// <param name="blogCredentials">An IBlogCredentialsAccessor</param>
|
||||
public static StaticSiteConfigFrontMatterKeys LoadKeysFromCredentials(IBlogCredentialsAccessor blogCredentials)
|
||||
{
|
||||
var frontMatterKeys = new StaticSiteConfigFrontMatterKeys();
|
||||
frontMatterKeys.LoadFromCredentials(blogCredentials);
|
||||
return frontMatterKeys;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
using OpenLiveWriter.CoreServices;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
public class StaticSiteConfigDetector
|
||||
{
|
||||
private static string[] IMG_DIR_CANDIDATES = new string[] { "images", "image", "img", "assets/img", "assets/images" };
|
||||
|
||||
private StaticSiteConfig config;
|
||||
private string localSitePath;
|
||||
|
||||
public StaticSiteConfigDetector(StaticSiteConfig config)
|
||||
{
|
||||
this.config = config;
|
||||
localSitePath = config.LocalSitePath;
|
||||
}
|
||||
|
||||
public bool DoDetect()
|
||||
{
|
||||
if (DoJekyllDetect()) return true;
|
||||
// More detection methods would be added here for the various static site generators
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempt detection for a Jekyll project
|
||||
/// </summary>
|
||||
/// <returns>True if Jekyll detection succeeded</returns>
|
||||
public bool DoJekyllDetect()
|
||||
{
|
||||
// First, check for a Gemfile specifying jekyll
|
||||
var gemfilePath = Path.Combine(localSitePath, "Gemfile");
|
||||
if (!File.Exists(gemfilePath)) return false;
|
||||
if (!File.ReadAllText(gemfilePath).Contains("jekyll")) return false;
|
||||
|
||||
// Find the config file
|
||||
var configPath = Path.Combine(localSitePath, "_config.yml");
|
||||
if (!File.Exists(configPath)) return false;
|
||||
|
||||
// Jekyll site detected, set defaults
|
||||
// Posts path is almost always _posts, check that it exists before setting
|
||||
if (Directory.Exists(Path.Combine(localSitePath, "_posts"))) config.PostsPath = "_posts";
|
||||
// Pages enabled and in root dir
|
||||
config.PagesEnabled = true;
|
||||
config.PagesPath = ".";
|
||||
// If a _site dir exists, assume site is locally built
|
||||
if (Directory.Exists(Path.Combine(localSitePath, "_site")))
|
||||
{
|
||||
config.BuildingEnabled = true;
|
||||
config.OutputPath = "_site";
|
||||
}
|
||||
// Check for all possible image upload directories
|
||||
foreach(var dir in IMG_DIR_CANDIDATES)
|
||||
{
|
||||
if (Directory.Exists(Path.Combine(localSitePath, dir)))
|
||||
{
|
||||
config.ImagesEnabled = true;
|
||||
config.ImagesPath = dir;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var yaml = new YamlStream();
|
||||
try
|
||||
{
|
||||
// Attempt to load the YAML document
|
||||
yaml.Load(new StringReader(File.ReadAllText(configPath)));
|
||||
var mapping = (YamlMappingNode)yaml.Documents[0].RootNode;
|
||||
|
||||
// Fill values from config
|
||||
// Site title
|
||||
var titleNode = mapping.Where(kv => kv.Key.ToString() == "title");
|
||||
if (titleNode.Count() > 0) config.SiteTitle = titleNode.First().Value.ToString();
|
||||
|
||||
// Homepage
|
||||
// Check for url node first
|
||||
var urlNode = mapping.Where(kv => kv.Key.ToString() == "url");
|
||||
if (urlNode.Count() > 0)
|
||||
{
|
||||
config.SiteUrl = urlNode.First().Value.ToString();
|
||||
// Now check for baseurl to apply to url
|
||||
var baseurlNode = mapping.Where(kv => kv.Key.ToString() == "baseurl");
|
||||
// Combine base url
|
||||
if (baseurlNode.Count() > 0) config.SiteUrl = UrlHelper.UrlCombine(config.SiteUrl, baseurlNode.First().Value.ToString());
|
||||
}
|
||||
|
||||
// Destination
|
||||
// If specified, local site building can be safely assumed to be enabled
|
||||
var destinationNode = mapping.Where(kv => kv.Key.ToString() == "destination");
|
||||
if(destinationNode.Count() > 0)
|
||||
{
|
||||
config.BuildingEnabled = true;
|
||||
config.OutputPath = destinationNode.First().Value.ToString();
|
||||
}
|
||||
} catch(Exception)
|
||||
{
|
||||
// YAML may be malformed, defaults are still set from above so return true
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool AttmeptAutoDetect(StaticSiteConfig config)
|
||||
=> new StaticSiteConfigDetector(config).DoDetect();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using System.IO;
|
||||
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using OpenLiveWriter.Localization;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
public class StaticSiteConfigValidator
|
||||
{
|
||||
private StaticSiteConfig _config;
|
||||
|
||||
public StaticSiteConfigValidator(StaticSiteConfig config)
|
||||
{
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public StaticSiteConfigValidator ValidateAll()
|
||||
=> this
|
||||
.ValidateLocalSitePath()
|
||||
.ValidatePostsPath()
|
||||
.ValidatePagesPath()
|
||||
.ValidateDraftsPath()
|
||||
.ValidateImagesPath()
|
||||
.ValidateOutputPath()
|
||||
.ValidateBuildCommand()
|
||||
.ValidatePublishCommand();
|
||||
|
||||
#region Path Validation
|
||||
|
||||
public StaticSiteConfigValidator ValidateLocalSitePath()
|
||||
{
|
||||
if(!Directory.Exists(_config.LocalSitePath))
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathLocalSitePathNotFound),
|
||||
_config.LocalSitePath);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public StaticSiteConfigValidator ValidatePostsPath()
|
||||
{
|
||||
var postsPathFull = $"{_config.LocalSitePath}\\{_config.PostsPath}";
|
||||
|
||||
// If the Posts path is empty, display an error
|
||||
if (_config.PostsPath.Trim() == string.Empty)
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathPostsEmpty));
|
||||
|
||||
// If the Posts path doesn't exist, display an error
|
||||
if (!Directory.Exists(postsPathFull))
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathPostsNotFound),
|
||||
postsPathFull);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public StaticSiteConfigValidator ValidatePagesPath()
|
||||
{
|
||||
if (!_config.PagesEnabled) return this; // Don't validate if pages aren't enabled
|
||||
|
||||
var pagesPathFull = $"{_config.LocalSitePath}\\{_config.PagesPath}";
|
||||
|
||||
// If the Pages path is empty, display an error
|
||||
if (_config.PagesPath.Trim() == string.Empty)
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathPagesEmpty));
|
||||
|
||||
// If the path doesn't exist, display an error
|
||||
if (!Directory.Exists(pagesPathFull))
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathPagesNotFound),
|
||||
pagesPathFull);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public StaticSiteConfigValidator ValidateDraftsPath()
|
||||
{
|
||||
if (!_config.DraftsEnabled) return this; // Don't validate if drafts aren't enabled
|
||||
|
||||
var draftsPathFull = $"{_config.LocalSitePath}\\{_config.DraftsPath}";
|
||||
|
||||
// If the Drafts path is empty, display an error
|
||||
if (_config.DraftsPath.Trim() == string.Empty)
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathDraftsEmpty));
|
||||
|
||||
// If the path doesn't exist, display an error
|
||||
if (!Directory.Exists(draftsPathFull))
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathDraftsNotFound),
|
||||
draftsPathFull);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public StaticSiteConfigValidator ValidateImagesPath()
|
||||
{
|
||||
if (!_config.ImagesEnabled) return this; // Don't validate if images aren't enabled
|
||||
|
||||
var imagesPathFull = $"{_config.LocalSitePath}\\{_config.ImagesPath}";
|
||||
|
||||
// If the Images path is empty, display an error
|
||||
if (_config.ImagesPath.Trim() == string.Empty)
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathImagesEmpty));
|
||||
|
||||
// If the path doesn't exist, display an error
|
||||
if (!Directory.Exists(imagesPathFull))
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathImagesNotFound),
|
||||
imagesPathFull);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public StaticSiteConfigValidator ValidateOutputPath()
|
||||
{
|
||||
if (!_config.BuildingEnabled) return this; // Don't validate if building isn't enabled
|
||||
|
||||
var outputPathFull = $"{_config.LocalSitePath}\\{_config.OutputPath}";
|
||||
|
||||
// If the Output path is empty, display an error
|
||||
if (_config.OutputPath.Trim() == string.Empty)
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathOutputEmpty));
|
||||
|
||||
// If the path doesn't exist, display an error
|
||||
if (!Directory.Exists(outputPathFull))
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPathFolderNotFound),
|
||||
Res.Get(StringId.SSGErrorPathOutputNotFound),
|
||||
outputPathFull);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public StaticSiteConfigValidator ValidateBuildCommand()
|
||||
{
|
||||
if (!_config.BuildingEnabled) return this; // Don't validate if building isn't enabled
|
||||
|
||||
if (_config.BuildCommand.Trim() == string.Empty)
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorBuildCommandEmptyTitle),
|
||||
Res.Get(StringId.SSGErrorBuildCommandEmptyText));
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public StaticSiteConfigValidator ValidatePublishCommand()
|
||||
{
|
||||
if (_config.PublishCommand.Trim() == string.Empty)
|
||||
throw new StaticSiteConfigValidationException(
|
||||
Res.Get(StringId.SSGErrorPublishCommandEmptyTitle),
|
||||
Res.Get(StringId.SSGErrorPublishCommandEmptyText));
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public class StaticSiteConfigValidationException : BlogClientException
|
||||
{
|
||||
public StaticSiteConfigValidationException(string title, string text, params object[] textFormatArgs) : base(title, text, textFormatArgs)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,258 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
|
||||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using OpenLiveWriter.Localization;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
public abstract class StaticSiteItem
|
||||
{
|
||||
/// <summary>
|
||||
/// The extension of published posts to the site project, including dot.
|
||||
/// </summary>
|
||||
public static string PUBLISH_FILE_EXTENSION = ".html";
|
||||
|
||||
private static Regex POST_PARSE_REGEX = new Regex("^---\r?\n((?:.*\r?\n)*?)---\r?\n\r?\n((?:.*\r?\n?)*)");
|
||||
|
||||
protected StaticSiteConfig SiteConfig;
|
||||
public BlogPost BlogPost { get; private set; }
|
||||
public bool IsDraft { get; set; } = false;
|
||||
|
||||
public StaticSiteItem(StaticSiteConfig config)
|
||||
{
|
||||
SiteConfig = config;
|
||||
BlogPost = null;
|
||||
}
|
||||
|
||||
public StaticSiteItem(StaticSiteConfig config, BlogPost blogPost)
|
||||
{
|
||||
SiteConfig = config;
|
||||
BlogPost = blogPost;
|
||||
}
|
||||
|
||||
public StaticSiteItem(StaticSiteConfig config, BlogPost blogPost, bool isDraft)
|
||||
{
|
||||
SiteConfig = config;
|
||||
BlogPost = blogPost;
|
||||
IsDraft = isDraft;
|
||||
}
|
||||
|
||||
public virtual StaticSiteItemFrontMatter FrontMatter
|
||||
{
|
||||
get => StaticSiteItemFrontMatter.GetFromBlogPost(SiteConfig.FrontMatterKeys, BlogPost);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the post to a string, ready to be written to disk
|
||||
/// </summary>
|
||||
/// <returns>String representation of the post, including front-matter, lines separated by LF</returns>
|
||||
public override string ToString()
|
||||
{
|
||||
var builder = new StringBuilder();
|
||||
builder.AppendLine("---");
|
||||
builder.Append(FrontMatter.ToString());
|
||||
builder.AppendLine("---");
|
||||
builder.AppendLine();
|
||||
builder.Append(BlogPost.Contents);
|
||||
return builder.ToString().Replace("\r\n", "\n");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Unique ID of the BlogPost
|
||||
/// </summary>
|
||||
public string Id
|
||||
{
|
||||
get => BlogPost.Id;
|
||||
set => BlogPost.Id = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The safe slug for the post
|
||||
/// </summary>
|
||||
public string Slug
|
||||
{
|
||||
get => _safeSlug;
|
||||
set => BlogPost.Slug = _safeSlug = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Confirmed safe slug; does not conflict with any existing post on disk or points to this post on disk.
|
||||
/// </summary>
|
||||
private string _safeSlug;
|
||||
|
||||
/// <summary>
|
||||
/// Get the current on-disk slug from the on-disk post with this ID
|
||||
/// </summary>
|
||||
public string DiskSlugFromFilePathById
|
||||
{
|
||||
get => FilePathById == null ? null : GetSlugFromPublishFileName(FilePathById);
|
||||
}
|
||||
|
||||
public DateTime DatePublished
|
||||
{
|
||||
get => BlogPost.HasDatePublishedOverride ? BlogPost.DatePublishedOverride : BlogPost.DatePublished;
|
||||
set => BlogPost.DatePublished = BlogPost.DatePublishedOverride = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the on-disk file path for the published post, based on slug
|
||||
/// </summary>
|
||||
public string FilePathBySlug
|
||||
{
|
||||
get => GetFilePathForProvidedSlug(Slug);
|
||||
}
|
||||
|
||||
protected string _filePathById;
|
||||
/// <summary>
|
||||
/// Get the on-disk file path for the published post, based on ID
|
||||
/// </summary>
|
||||
public abstract string FilePathById
|
||||
{
|
||||
get;
|
||||
protected set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the site path for the published item
|
||||
/// eg. /2019/01/slug.html
|
||||
/// </summary>
|
||||
public abstract string SitePath { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Generate a safe slug if the post doesn't already have one. Returns the current or new Slug.
|
||||
/// </summary>
|
||||
/// <returns>The current or new Slug.</returns>
|
||||
public string EnsureSafeSlug()
|
||||
{
|
||||
if (_safeSlug == null || _safeSlug == string.Empty) Slug = FindNewSlug(BlogPost.Slug, safe: true);
|
||||
return Slug;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a new Id and save it to the BlogPost if requried. Returns the current or new Id.
|
||||
/// </summary>
|
||||
/// <returns>The current or new Id</returns>
|
||||
public string EnsureId()
|
||||
{
|
||||
if(Id == null || Id == string.Empty) Id = Guid.NewGuid().ToString();
|
||||
return Id;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set post published DateTime to current DateTime if one isn't already set, or current one is default.
|
||||
/// </summary>
|
||||
/// <returns>The current or new DatePublished.</returns>
|
||||
public DateTime EnsureDatePublished()
|
||||
{
|
||||
if (DatePublished == null || DatePublished == new DateTime(1, 1, 1)) DatePublished = DateTime.UtcNow;
|
||||
return DatePublished;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generate a slug for this post based on it's title or a preferred slug
|
||||
/// </summary>
|
||||
/// <param name="preferredSlug">The text to base the preferred slug off of. default: post title</param>
|
||||
/// <param name="safe">Safe mode; if true the returned slug will not conflict with any existing file</param>
|
||||
/// <returns>An on-disk slug for this post</returns>
|
||||
public string FindNewSlug(string preferredSlug, bool safe)
|
||||
{
|
||||
// Try the filename without a duplicate identifier, then duplicate identifiers up until 999 before throwing an exception
|
||||
for(int i = 0; i < 1000; i++)
|
||||
{
|
||||
// "Hello World!" -> "hello-world"
|
||||
string slug = StaticSiteClient.WEB_UNSAFE_CHARS
|
||||
.Replace((preferredSlug == string.Empty ? BlogPost.Title : preferredSlug).ToLower(), "")
|
||||
.Replace(" ", "-");
|
||||
if (!safe) return slug; // If unsafe mode, return the generated slug immediately.
|
||||
|
||||
if (i > 0) slug += $"-{i}";
|
||||
if (!File.Exists(GetFilePathForProvidedSlug(slug))) return slug;
|
||||
}
|
||||
|
||||
// Couldn't find an available filename, use the post's ID.
|
||||
return StaticSiteClient.WEB_UNSAFE_CHARS.Replace(EnsureId(), "").Replace(" ", "-");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the on-disk filename for the provided slug
|
||||
/// </summary>
|
||||
/// <param name="slug">Post slug</param>
|
||||
/// <returns>The on-disk filename</returns>
|
||||
protected abstract string GetFileNameForProvidedSlug(string slug);
|
||||
|
||||
/// <summary>
|
||||
/// Get the on-disk path for the provided slug
|
||||
/// </summary>
|
||||
/// <param name="slug">Post slug</param>
|
||||
/// <returns>The on-disk path, including filename from GetFileNameForProvidedSlug</returns>
|
||||
protected abstract string GetFilePathForProvidedSlug(string slug);
|
||||
|
||||
protected abstract string GetSlugFromPublishFileName(string publishFileName);
|
||||
|
||||
/// <summary>
|
||||
/// If the item is a Post and a Draft, returns the Drafts dir, otherwise returns the regular dir
|
||||
/// </summary>
|
||||
protected string ItemRelativeDir =>
|
||||
IsDraft && !BlogPost.IsPage && SiteConfig.DraftsEnabled ?
|
||||
SiteConfig.DraftsPath
|
||||
: (
|
||||
BlogPost.IsPage ?
|
||||
SiteConfig.PagesPath
|
||||
:
|
||||
SiteConfig.PostsPath
|
||||
);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Save the post to the correct directory
|
||||
/// </summary>
|
||||
public void SaveToFile(string postFilePath)
|
||||
{
|
||||
// Save the post to disk
|
||||
File.WriteAllText(postFilePath, ToString());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load published post from a specified file path
|
||||
/// </summary>
|
||||
/// <param name="postFilePath">Path to published post file</param>
|
||||
public virtual void LoadFromFile(string postFilePath)
|
||||
{
|
||||
// Attempt to load file contents
|
||||
var fileContents = File.ReadAllText(postFilePath);
|
||||
|
||||
// Parse out everything between triple-hyphens into front matter parser
|
||||
var frontMatterMatchResult = POST_PARSE_REGEX.Match(fileContents);
|
||||
if (!frontMatterMatchResult.Success || frontMatterMatchResult.Groups.Count < 3)
|
||||
throw new BlogClientException(Res.Get(StringId.SSGErrorItemLoadTitle), Res.Get(StringId.SSGErrorItemLoadTextFM));
|
||||
var frontMatterYaml = frontMatterMatchResult.Groups[1].Value;
|
||||
var postContent = frontMatterMatchResult.Groups[2].Value;
|
||||
|
||||
// Create a new BlogPost
|
||||
BlogPost = new BlogPost();
|
||||
// Parse front matter and save in
|
||||
StaticSiteItemFrontMatter.GetFromYaml(SiteConfig.FrontMatterKeys, frontMatterYaml).SaveToBlogPost(BlogPost);
|
||||
|
||||
// Throw error if post does not have an ID
|
||||
if (Id == null || Id == string.Empty)
|
||||
throw new BlogClientException(Res.Get(StringId.SSGErrorItemLoadTitle), Res.Get(StringId.SSGErrorItemLoadTextId));
|
||||
|
||||
// FilePathById will be the path we loaded this post from
|
||||
FilePathById = postFilePath;
|
||||
|
||||
// Load the content into blogpost
|
||||
BlogPost.Contents = postContent;
|
||||
|
||||
// Set slug to match file name
|
||||
Slug = GetSlugFromPublishFileName(Path.GetFileName(postFilePath));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
public class StaticSiteItemFrontMatter
|
||||
{
|
||||
private StaticSiteConfigFrontMatterKeys _frontMatterKeys;
|
||||
|
||||
public string Id { get; set; }
|
||||
public string Title { get; set; }
|
||||
public string Date { get; set; }
|
||||
public string Layout { get; set; } = "post";
|
||||
public string Slug { get; set; }
|
||||
public string[] Tags { get; set; }
|
||||
public string ParentId { get; set; } = "";
|
||||
public string Permalink { get; set; }
|
||||
|
||||
public StaticSiteItemFrontMatter(StaticSiteConfigFrontMatterKeys frontMatterKeys)
|
||||
{
|
||||
_frontMatterKeys = frontMatterKeys;
|
||||
Tags = new string[] { }; // Initialize Tags to empty array
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the front matter to it's YAML representation
|
||||
/// </summary>
|
||||
/// <returns>YAML representation of post front-matter, lines separated by CRLF</returns>
|
||||
public string Serialize()
|
||||
{
|
||||
var root = new YamlMappingNode();
|
||||
|
||||
if (Id != null && Id.Length > 0) root.Add(_frontMatterKeys.IdKey, Id);
|
||||
if (Title != null) root.Add(_frontMatterKeys.TitleKey, Title);
|
||||
if(Date != null) root.Add(_frontMatterKeys.DateKey, Date);
|
||||
if(Layout != null) root.Add(_frontMatterKeys.LayoutKey, Layout);
|
||||
if (Tags != null && Tags.Length > 0)
|
||||
root.Add(_frontMatterKeys.TagsKey, new YamlSequenceNode(Tags.Select(
|
||||
tag => new YamlScalarNode(tag))));
|
||||
if (!string.IsNullOrEmpty(ParentId)) root.Add(_frontMatterKeys.ParentIdKey, ParentId);
|
||||
if (!string.IsNullOrEmpty(Permalink)) root.Add(_frontMatterKeys.PermalinkKey, Permalink);
|
||||
|
||||
var stream = new YamlStream(new YamlDocument(root));
|
||||
var stringWriter = new StringWriter();
|
||||
stream.Save(stringWriter);
|
||||
// Trim off end-of-doc
|
||||
return new Regex("\\.\\.\\.\r\n$").Replace(stringWriter.ToString(), "", 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the front matter to it's YAML representation
|
||||
/// </summary>
|
||||
/// <returns>YAML representation of post front-matter, lines separated by CRLF</returns>
|
||||
public override string ToString() => Serialize();
|
||||
|
||||
public void Deserialize(string yaml)
|
||||
{
|
||||
var stream = new YamlStream();
|
||||
stream.Load(new StringReader(yaml));
|
||||
var root = (YamlMappingNode)stream.Documents[0].RootNode;
|
||||
|
||||
// Load id
|
||||
var idNodes = root.Where(kv => kv.Key.ToString() == _frontMatterKeys.IdKey);
|
||||
if (idNodes.Count() > 0) Id = idNodes.First().Value.ToString();
|
||||
|
||||
// Load title
|
||||
var titleNodes = root.Where(kv => kv.Key.ToString() == _frontMatterKeys.TitleKey);
|
||||
if (titleNodes.Count() > 0) Title = titleNodes.First().Value.ToString();
|
||||
|
||||
// Load date
|
||||
var dateNodes = root.Where(kv => kv.Key.ToString() == _frontMatterKeys.DateKey);
|
||||
if (dateNodes.Count() > 0) Date = dateNodes.First().Value.ToString();
|
||||
|
||||
// Load layout
|
||||
var layoutNodes = root.Where(kv => kv.Key.ToString() == _frontMatterKeys.LayoutKey);
|
||||
if (layoutNodes.Count() > 0) Layout = layoutNodes.First().Value.ToString();
|
||||
|
||||
// Load tags
|
||||
var tagNodes = root.Where(kv => kv.Key.ToString() == _frontMatterKeys.TagsKey);
|
||||
if (tagNodes.Count() > 0 && tagNodes.First().Value.NodeType == YamlNodeType.Sequence)
|
||||
Tags = ((YamlSequenceNode)tagNodes.First().Value).Select(node => node.ToString()).ToArray();
|
||||
|
||||
// Load parent ID
|
||||
var parentIdNodes = root.Where(kv => kv.Key.ToString() == _frontMatterKeys.ParentIdKey);
|
||||
if (parentIdNodes.Count() > 0) ParentId = parentIdNodes.First().Value.ToString();
|
||||
|
||||
// Permalink is never loaded, only saved
|
||||
}
|
||||
|
||||
public void LoadFromBlogPost(BlogPost post)
|
||||
{
|
||||
Id = post.Id;
|
||||
Title = post.Title;
|
||||
Tags = post.Categories.Union(post.NewCategories).Select(cat => cat.Name).ToArray();
|
||||
Date = (post.HasDatePublishedOverride ? post.DatePublishedOverride : post.DatePublished)
|
||||
.ToString("yyyy-MM-dd HH:mm:ss");
|
||||
Layout = post.IsPage ? "page" : "post";
|
||||
if(post.IsPage) ParentId = post.PageParent.Id;
|
||||
}
|
||||
|
||||
public void SaveToBlogPost(BlogPost post)
|
||||
{
|
||||
post.Id = Id;
|
||||
post.Title = Title;
|
||||
post.Categories = Tags?.Select(t => new BlogPostCategory(t)).ToArray();
|
||||
try { post.DatePublished = post.DatePublishedOverride = DateTime.Parse(Date); } catch { }
|
||||
post.IsPage = Layout == "page";
|
||||
if (post.IsPage) post.PageParent = new PostIdAndNameField(ParentId, string.Empty);
|
||||
|
||||
}
|
||||
|
||||
public static StaticSiteItemFrontMatter GetFromBlogPost(StaticSiteConfigFrontMatterKeys frontMatterKeys, BlogPost post)
|
||||
{
|
||||
var frontMatter = new StaticSiteItemFrontMatter(frontMatterKeys);
|
||||
frontMatter.LoadFromBlogPost(post);
|
||||
return frontMatter;
|
||||
}
|
||||
|
||||
public static StaticSiteItemFrontMatter GetFromYaml(StaticSiteConfigFrontMatterKeys frontMatterKeys, string yaml)
|
||||
{
|
||||
var frontMatter = new StaticSiteItemFrontMatter(frontMatterKeys);
|
||||
frontMatter.Deserialize(yaml);
|
||||
return frontMatter;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,195 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
|
||||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
public class StaticSitePage : StaticSiteItem
|
||||
{
|
||||
// Matches the published slug out of a on-disk page
|
||||
// page-test_sub-page-test.html -> sub-page-test
|
||||
// 0001-01-01-page-test.html -> 0001-01-01-page-test
|
||||
// _pages\my-page.html -> my-page
|
||||
private static Regex FILENAME_SLUG_REGEX = new Regex(@"^(?:(?:.*?)(?:\\|\/|_))*(.*?)\" + PUBLISH_FILE_EXTENSION + "$");
|
||||
|
||||
private static int PARENT_CRAWL_MAX_LEVELS = 32;
|
||||
|
||||
public StaticSitePage(StaticSiteConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
public StaticSitePage(StaticSiteConfig config, BlogPost blogPost) : base(config, blogPost)
|
||||
{
|
||||
}
|
||||
|
||||
public PageInfo PageInfo
|
||||
{
|
||||
get => new PageInfo(BlogPost.Id, BlogPost.Title, DatePublished, BlogPost.PageParent?.Id);
|
||||
}
|
||||
|
||||
protected override string GetSlugFromPublishFileName(string publishFileName) => FILENAME_SLUG_REGEX.Match(publishFileName).Groups[1].Value;
|
||||
|
||||
public override StaticSiteItemFrontMatter FrontMatter
|
||||
{
|
||||
get
|
||||
{
|
||||
var fm = base.FrontMatter;
|
||||
fm.Permalink = SitePath;
|
||||
return fm;
|
||||
}
|
||||
}
|
||||
|
||||
public override string FilePathById
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_filePathById != null) return _filePathById;
|
||||
var foundFile = Directory.GetFiles(Path.Combine(SiteConfig.LocalSitePath, SiteConfig.PagesPath), "*.html")
|
||||
.Where(pageFile =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var page = LoadFromFile(Path.Combine(SiteConfig.LocalSitePath, SiteConfig.PagesPath, pageFile), SiteConfig);
|
||||
if (page.Id == Id) return true;
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
}).DefaultIfEmpty(null).FirstOrDefault();
|
||||
return _filePathById = (foundFile == null ? null : Path.Combine(SiteConfig.LocalSitePath, SiteConfig.PagesPath, foundFile));
|
||||
}
|
||||
|
||||
protected set => _filePathById = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the site path ("permalink") for the published page
|
||||
/// eg. /about/, /page/sub-page/
|
||||
/// </summary>
|
||||
public override string SitePath
|
||||
{
|
||||
get
|
||||
{
|
||||
// Get slug for all parent posts and prepend
|
||||
var parentSlugs = string.Join("/", GetParentSlugs());
|
||||
if (parentSlugs != string.Empty) parentSlugs += "/"; // If parent slugs were collected, append slug separator
|
||||
|
||||
return $"/{parentSlugs}{Slug}/"; // parentSlugs will include tailing slash
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets on-disk filename based on slug
|
||||
/// </summary>
|
||||
/// <param name="slug">Post slug</param>
|
||||
/// <returns>File name with prepended date</returns>
|
||||
protected override string GetFileNameForProvidedSlug(string slug)
|
||||
{
|
||||
var parentSlugs = string.Join("_", GetParentSlugs());
|
||||
if (parentSlugs != string.Empty) parentSlugs += "_"; // If parent slugs were collected, append slug separator
|
||||
return $"{parentSlugs}{slug}{PUBLISH_FILE_EXTENSION}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a path based on file name and posts path
|
||||
/// </summary>
|
||||
/// <param name="slug"></param>
|
||||
/// <returns>Path containing pages path</returns>
|
||||
protected override string GetFilePathForProvidedSlug(string slug)
|
||||
{
|
||||
return Path.Combine(
|
||||
SiteConfig.LocalSitePath,
|
||||
SiteConfig.PagesPath,
|
||||
GetFileNameForProvidedSlug(slug));
|
||||
}
|
||||
|
||||
public StaticSitePage ResolveParent()
|
||||
{
|
||||
if(!BlogPost.PageParent.IsEmpty)
|
||||
{
|
||||
// Attempt to locate and load parent
|
||||
var parent = GetPageById(SiteConfig, BlogPost.PageParent.Id);
|
||||
if (parent == null)
|
||||
{
|
||||
// Parent not found, set PageParent to empty
|
||||
BlogPost.PageParent = PostIdAndNameField.Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Populate Name field
|
||||
BlogPost.PageParent = new PostIdAndNameField(parent.Id, parent.BlogPost.Title);
|
||||
}
|
||||
return parent;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Crawl parent tree and collect all slugs
|
||||
/// </summary>
|
||||
/// <returns>An array of strings containing the slugs of all parents, in order.</returns>
|
||||
private string[] GetParentSlugs()
|
||||
{
|
||||
List<string> parentSlugs = new List<string>();
|
||||
|
||||
var parentId = BlogPost.PageParent.Id;
|
||||
int level = 0;
|
||||
while (!string.IsNullOrEmpty(parentId) && level < PARENT_CRAWL_MAX_LEVELS)
|
||||
{
|
||||
var parent = GetPageById(SiteConfig, parentId);
|
||||
if (parent == null)
|
||||
throw new BlogClientException(
|
||||
"Page parent not found",
|
||||
"Could not locate parent for page '{0}' with specified parent ID.",
|
||||
BlogPost.Title);
|
||||
parentSlugs.Insert(0, parent.Slug);
|
||||
|
||||
parentId = parent.BlogPost.PageParent.Id;
|
||||
level++;
|
||||
}
|
||||
|
||||
return parentSlugs.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load published page from a specified file path
|
||||
/// </summary>
|
||||
/// <param name="pageFilePath">Path to published page file</param>
|
||||
/// <param name="config">StaticSiteConfig to instantiate page with</param>
|
||||
/// <returns>A loaded StaticSitePage</returns>
|
||||
public static StaticSitePage LoadFromFile(string pageFilePath, StaticSiteConfig config)
|
||||
{
|
||||
var page = new StaticSitePage(config);
|
||||
page.LoadFromFile(pageFilePath);
|
||||
return page;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all valid pages in PagesPath
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerable of StaticSitePage</returns>
|
||||
public static IEnumerable<StaticSitePage> GetAllPages(StaticSiteConfig config) =>
|
||||
Directory.GetFiles(Path.Combine(config.LocalSitePath, config.PagesPath), "*.html")
|
||||
.Select(pageFile =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return LoadFromFile(Path.Combine(config.LocalSitePath, config.PagesPath, pageFile), config);
|
||||
}
|
||||
catch { return null; }
|
||||
})
|
||||
.Where(p => p != null);
|
||||
|
||||
public static StaticSitePage GetPageById(StaticSiteConfig config, string id)
|
||||
=> GetAllPages(config).Where(page => page.Id == id).DefaultIfEmpty(null).FirstOrDefault();
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System.IO;
|
||||
using System.Diagnostics;
|
||||
|
||||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Clients.StaticSite
|
||||
{
|
||||
public class StaticSitePost : StaticSiteItem
|
||||
{
|
||||
// Matches the published slug out of a on-disk post
|
||||
// 2014-02-02-test.html -> test
|
||||
// _posts\2014-02-02-my-post-test.html -> my-post-test
|
||||
private static Regex FILENAME_SLUG_REGEX = new Regex(@"^(?:(?:.*?)(?:\\|\/))*(?:\d\d\d\d-\d\d-\d\d-)(.*?)\" + PUBLISH_FILE_EXTENSION + "$");
|
||||
|
||||
public StaticSitePost(StaticSiteConfig config) : base(config)
|
||||
{
|
||||
}
|
||||
|
||||
public StaticSitePost(StaticSiteConfig config, BlogPost blogPost) : base(config, blogPost)
|
||||
{
|
||||
}
|
||||
|
||||
public StaticSitePost(StaticSiteConfig config, BlogPost blogPost, bool isDraft) : base(config, blogPost, isDraft)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string GetSlugFromPublishFileName(string publishFileName) => FILENAME_SLUG_REGEX.Match(publishFileName).Groups[1].Value;
|
||||
|
||||
public override string FilePathById {
|
||||
get
|
||||
{
|
||||
if (_filePathById != null) return _filePathById;
|
||||
|
||||
var foundFile = Directory.GetFiles(Path.Combine(SiteConfig.LocalSitePath, ItemRelativeDir), "*.html")
|
||||
.Where(postFile =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var post = LoadFromFile(Path.Combine(SiteConfig.LocalSitePath, ItemRelativeDir, postFile), SiteConfig);
|
||||
if (post.Id == Id) return true;
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
}).DefaultIfEmpty(null).FirstOrDefault();
|
||||
return _filePathById = (foundFile == null ? null : Path.Combine(SiteConfig.LocalSitePath, ItemRelativeDir, foundFile));
|
||||
}
|
||||
|
||||
protected set => _filePathById = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// We currently do not take configuration for specifiying a post path format
|
||||
/// </summary>
|
||||
public override string SitePath => throw new NotImplementedException();
|
||||
|
||||
/// <summary>
|
||||
/// Gets filename based on slug with prepended date
|
||||
/// </summary>
|
||||
/// <param name="slug">Post slug</param>
|
||||
/// <returns>File name with prepended date</returns>
|
||||
protected override string GetFileNameForProvidedSlug(string slug)
|
||||
{
|
||||
return $"{DatePublished.ToString("yyyy-MM-dd")}-{slug}{PUBLISH_FILE_EXTENSION}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a path based on file name and posts path
|
||||
/// </summary>
|
||||
/// <param name="slug"></param>
|
||||
/// <returns>Path containing posts path</returns>
|
||||
protected override string GetFilePathForProvidedSlug(string slug)
|
||||
{
|
||||
return Path.Combine(
|
||||
SiteConfig.LocalSitePath,
|
||||
ItemRelativeDir,
|
||||
GetFileNameForProvidedSlug(slug));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load published post from a specified file path
|
||||
/// </summary>
|
||||
/// <param name="postFilePath">Path to published post file</param>
|
||||
/// <param name="config">StaticSiteConfig to instantiate post with</param>
|
||||
/// <returns>A loaded StaticSitePost</returns>
|
||||
public static StaticSitePost LoadFromFile(string postFilePath, StaticSiteConfig config)
|
||||
{
|
||||
var post = new StaticSitePost(config);
|
||||
post.LoadFromFile(postFilePath);
|
||||
return post;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all valid posts in PostsPath
|
||||
/// </summary>
|
||||
/// <returns>An IEnumerable of StaticSitePost</returns>
|
||||
public static IEnumerable<StaticSiteItem> GetAllPosts(StaticSiteConfig config, bool includeDrafts) =>
|
||||
Directory.GetFiles(Path.Combine(config.LocalSitePath, config.PostsPath), "*.html")
|
||||
.Select(fileName => Path.Combine(config.LocalSitePath, config.PostsPath, fileName)) // Create full paths
|
||||
.Concat(includeDrafts && config.DraftsEnabled ? // Collect drafts if they're enabled
|
||||
Directory.GetFiles(Path.Combine(config.LocalSitePath, config.DraftsPath), "*.html")
|
||||
.Select(fileName => Path.Combine(config.LocalSitePath, config.DraftsPath, fileName)) // Create full paths
|
||||
:
|
||||
new string[] { } // Drafts are not enabled or were not requested
|
||||
)
|
||||
.Select(postFile =>
|
||||
{
|
||||
try
|
||||
{
|
||||
return LoadFromFile(postFile, config);
|
||||
}
|
||||
catch { return null; }
|
||||
})
|
||||
.Where(p => p != null);
|
||||
|
||||
public static StaticSiteItem GetPostById(StaticSiteConfig config, string id)
|
||||
=> GetAllPosts(config, true).Where(post => post.Id == id).DefaultIfEmpty(null).FirstOrDefault();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -178,7 +178,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
protected virtual void BeforeHttpRequest(HttpWebRequest request)
|
||||
{
|
||||
// WARNING: Derived classes do not currently make it a practice to call this method
|
||||
// so don't count on the code executing if the method is overriden!
|
||||
// so don't count on the code executing if the method is overridden!
|
||||
}
|
||||
|
||||
public virtual BlogPostCategory[] SuggestCategories(string blogId, string partialCategoryName)
|
||||
|
@ -300,7 +300,7 @@ namespace OpenLiveWriter.BlogClient.Clients
|
|||
/// If the date does not have a timezone designator then it will be presumed to be in the
|
||||
/// local time zone of this PC. This method is virtual so that for weblogs that produce
|
||||
/// undesignated date/time strings in some other timezone (like the timezone of the
|
||||
/// hosting providor) subclasses can do whatever offset is appropriate.
|
||||
/// hosting provider) subclasses can do whatever offset is appropriate.
|
||||
/// </summary>
|
||||
/// <param name="xmlNode"></param>
|
||||
/// <returns></returns>
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for details.
|
||||
|
||||
using mshtml;
|
||||
using OpenLiveWriter.BlogClient.Clients;
|
||||
using OpenLiveWriter.Controls;
|
||||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.CoreServices.Progress;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using OpenLiveWriter.Localization;
|
||||
using OpenLiveWriter.Mshtml;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Diagnostics;
|
||||
using System.ComponentModel;
|
||||
using System.Drawing;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
|
@ -12,18 +19,7 @@ using System.Net;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using mshtml;
|
||||
using OpenLiveWriter.BlogClient;
|
||||
using OpenLiveWriter.BlogClient.Clients;
|
||||
using OpenLiveWriter.Extensibility.BlogClient;
|
||||
using OpenLiveWriter.HtmlParser.Parser;
|
||||
using OpenLiveWriter.Localization;
|
||||
using OpenLiveWriter.Mshtml;
|
||||
using OpenLiveWriter.Controls;
|
||||
using OpenLiveWriter.CoreServices;
|
||||
using OpenLiveWriter.CoreServices.Progress;
|
||||
|
||||
namespace OpenLiveWriter.BlogClient.Detection
|
||||
{
|
||||
|
@ -158,6 +154,8 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
}
|
||||
private Exception _exception;
|
||||
|
||||
private string _nextTryPostUrl;
|
||||
|
||||
public object DetectTemplate(IProgressHost progress)
|
||||
{
|
||||
// if our context has not been set then just return without doing anything
|
||||
|
@ -181,7 +179,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
// try explicit detection of templates
|
||||
BlogEditingTemplateFiles templateFiles = SafeGetTemplates(new ProgressTick(progress, 50, 100));
|
||||
|
||||
// see if we got the FramedTempalte
|
||||
// see if we got the FramedTemplate
|
||||
if (templateFiles.FramedTemplate != null)
|
||||
blogTemplateFiles.Add(templateFiles.FramedTemplate);
|
||||
else
|
||||
|
@ -216,7 +214,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
_blogTemplateFiles = blogTemplateFiles.ToArray(typeof(BlogEditingTemplateFile)) as BlogEditingTemplateFile[];
|
||||
|
||||
// if we got at least one template by some method then clear any exception
|
||||
// that occurs so we can at least update that tempalte
|
||||
// that occurs so we can at least update that template
|
||||
_exception = null;
|
||||
}
|
||||
|
||||
|
@ -385,10 +383,10 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
BlogPostRegionLocatorStrategy regionLocatorStrategy = regionLocatorStrategies[i];
|
||||
try
|
||||
{
|
||||
blogTemplateFiles = GetBlogTemplateFiles(progress, regionLocatorStrategy, templateStrategies, targetTemplateTypes);
|
||||
blogTemplateFiles = GetBlogTemplateFiles(progress, regionLocatorStrategy, templateStrategies, targetTemplateTypes, _blogHomepageUrl);
|
||||
progress.UpdateProgress(100, 100);
|
||||
|
||||
//if any exception occured along the way, clear them since one of the template strategies
|
||||
//if any exception occurred along the way, clear them since one of the template strategies
|
||||
//was successful.
|
||||
_exception = null;
|
||||
}
|
||||
|
@ -428,7 +426,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
}
|
||||
|
||||
// return the detected tempaltes
|
||||
// return the detected templates
|
||||
return blogTemplateFiles;
|
||||
}
|
||||
|
||||
|
@ -439,8 +437,12 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
/// <param name="regionLocatorStrategy"></param>
|
||||
/// <param name="templateStrategies"></param>
|
||||
/// <param name="templateTypes"></param>
|
||||
/// <param name="targetUrl">
|
||||
/// The URL to analyze. If a post can be located, but not the body, this is used
|
||||
/// to reiterate into the post it fetch it's content directly.
|
||||
/// </param>
|
||||
/// <returns></returns>
|
||||
private BlogEditingTemplateFile[] GetBlogTemplateFiles(IProgressHost progress, BlogPostRegionLocatorStrategy regionLocatorStrategy, BlogEditingTemplateStrategy[] templateStrategies, BlogEditingTemplateType[] templateTypes)
|
||||
private BlogEditingTemplateFile[] GetBlogTemplateFiles(IProgressHost progress, BlogPostRegionLocatorStrategy regionLocatorStrategy, BlogEditingTemplateStrategy[] templateStrategies, BlogEditingTemplateType[] templateTypes, string targetUrl)
|
||||
{
|
||||
BlogEditingTemplateFile[] blogTemplateFiles = null;
|
||||
try
|
||||
|
@ -457,10 +459,27 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
CheckCancelRequested(parseTick);
|
||||
templateStrategy = templateStrategies[i];
|
||||
|
||||
// Clear _nextTryPostUrl flag
|
||||
_nextTryPostUrl = null;
|
||||
|
||||
// Parse the blog post HTML into an editing template.
|
||||
// Note: we can't use MarkupServices to parse the document from a non-UI thread,
|
||||
// so we have to execute the parsing portion of the template download operation on the UI thread.
|
||||
string editingTemplate = ParseWebpageIntoEditingTemplate_OnUIThread(_parentControl, regionLocatorStrategy, new ProgressTick(parseTick, 1, 5));
|
||||
string editingTemplate = ParseWebpageIntoEditingTemplate_OnUIThread(_parentControl, regionLocatorStrategy, new ProgressTick(parseTick, 1, 5), targetUrl);
|
||||
|
||||
// If there's no editing template, there should be a URL to try next
|
||||
Debug.Assert(editingTemplate != null || (editingTemplate == null && _nextTryPostUrl != null));
|
||||
|
||||
// If the homepage has just been analysed and the _nextTryPostUrl flag is set
|
||||
if (targetUrl == _blogHomepageUrl && _nextTryPostUrl != null && regionLocatorStrategy.CanRefetchPage)
|
||||
{
|
||||
// Try fetching the URL that has been specified, and reparse
|
||||
progress.UpdateProgress(Res.Get(StringId.ProgressDownloadingWeblogEditingStyleDeep));
|
||||
// Fetch the post page
|
||||
regionLocatorStrategy.FetchTemporaryPostPage(SilentProgressHost.Instance, _nextTryPostUrl);
|
||||
// Parse out the template
|
||||
editingTemplate = ParseWebpageIntoEditingTemplate_OnUIThread(_parentControl, regionLocatorStrategy, new ProgressTick(parseTick, 1, 5), _nextTryPostUrl);
|
||||
}
|
||||
|
||||
// check for cancel
|
||||
CheckCancelRequested(parseTick);
|
||||
|
@ -540,19 +559,48 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
/// <param name="uiContext"></param>
|
||||
/// <param name="progress"></param>
|
||||
/// <returns></returns>
|
||||
private string ParseWebpageIntoEditingTemplate_OnUIThread(Control uiContext, BlogPostRegionLocatorStrategy regionLocator, IProgressHost progress)
|
||||
private string ParseWebpageIntoEditingTemplate_OnUIThread(Control uiContext, BlogPostRegionLocatorStrategy regionLocator, IProgressHost progress, string postUrl)
|
||||
{
|
||||
BlogEditingTemplate blogEditingTemplate = (BlogEditingTemplate)uiContext.Invoke(new TemplateParser(ParseBlogPostIntoTemplate), new object[] { regionLocator, new ProgressTick(progress, 1, 100) });
|
||||
return blogEditingTemplate.Template;
|
||||
BlogEditingTemplate blogEditingTemplate = (BlogEditingTemplate)uiContext.Invoke(
|
||||
new TemplateParser(ParseBlogPostIntoTemplate),
|
||||
new object[] {
|
||||
regionLocator,
|
||||
new ProgressTick(progress, 1, 100),
|
||||
postUrl });
|
||||
return blogEditingTemplate?.Template;
|
||||
}
|
||||
private delegate BlogEditingTemplate TemplateParser(BlogPostRegionLocatorStrategy regionLocator, IProgressHost progress);
|
||||
private delegate BlogEditingTemplate TemplateParser(BlogPostRegionLocatorStrategy regionLocator, IProgressHost progress, string postUrl);
|
||||
|
||||
private BlogEditingTemplate ParseBlogPostIntoTemplate(BlogPostRegionLocatorStrategy regionLocator, IProgressHost progress)
|
||||
private BlogEditingTemplate ParseBlogPostIntoTemplate(BlogPostRegionLocatorStrategy regionLocator, IProgressHost progress, string postUrl)
|
||||
{
|
||||
progress.UpdateProgress(Res.Get(StringId.ProgressCreatingEditingTemplate));
|
||||
|
||||
BlogPostRegions regions = regionLocator.LocateRegionsOnUIThread(progress);
|
||||
BlogPostRegions regions = regionLocator.LocateRegionsOnUIThread(progress, postUrl);
|
||||
IHTMLElement primaryTitleRegion = GetPrimaryEditableTitleElement(regions.BodyRegion, regions.Document, regions.TitleRegions);
|
||||
|
||||
// IF
|
||||
// - primaryTitleRegion is not null (title found)
|
||||
// - BodyRegion is null (no post body found)
|
||||
// - AND primaryTitleRegion is a link
|
||||
if (primaryTitleRegion != null && regions.BodyRegion == null && primaryTitleRegion.tagName.ToLower() == "a")
|
||||
{
|
||||
// Title region was detected, but body region was not.
|
||||
// It is possible that only titles are shown on the homepage
|
||||
// Try requesting the post itself, and loading regions from the post itself
|
||||
|
||||
// HACK Somewhere the 'about:' protocol replaces http/https, replace it again with the correct protocol
|
||||
var pathMatch = new Regex("^about:(.*)$").Match((primaryTitleRegion as IHTMLAnchorElement).href);
|
||||
Debug.Assert(pathMatch.Success); // Assert that this URL is to the format we expect
|
||||
var newPostPath = pathMatch.Groups[1].Value; // Grab the path from the URL
|
||||
var homepageUri = new Uri(_blogHomepageUrl);
|
||||
var newPostUrl = $"{homepageUri.Scheme}://{homepageUri.Host}{newPostPath}"; // Recreate the full post URL
|
||||
|
||||
// Set the NextTryPostUrl flag in the region locater
|
||||
// This will indicate to the other thread that another page should be parsed
|
||||
_nextTryPostUrl = newPostUrl;
|
||||
return null;
|
||||
}
|
||||
|
||||
BlogEditingTemplate template = GenerateBlogTemplate((IHTMLDocument3)regions.Document, primaryTitleRegion, regions.TitleRegions, regions.BodyRegion);
|
||||
|
||||
progress.UpdateProgress(100, 100);
|
||||
|
@ -696,7 +744,6 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
// return value
|
||||
private BlogEditingTemplateFile[] _blogTemplateFiles = new BlogEditingTemplateFile[0];
|
||||
private Color? _postBodyBackgroundColor;
|
||||
|
||||
}
|
||||
|
||||
public delegate HttpWebResponse PageDownloader(string url, int timeoutMs);
|
||||
|
|
|
@ -923,7 +923,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
//IE won't return some attributes (like class) using IHTMLElement.getAttribute(),
|
||||
//so if the value is null, try to get the value directly from the DOM Attribute.
|
||||
//Note: we can't use the DOM value by default, because IE will rewrite the value
|
||||
//to contain a fully-qualified path on some attribures (like src and href).
|
||||
//to contain a fully-qualified path on some attributes (like src and href).
|
||||
attrValue = attr.nodeValue as string;
|
||||
|
||||
if (attrValue == null)
|
||||
|
@ -1134,7 +1134,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
// vertical-align: baseline;
|
||||
// }
|
||||
//
|
||||
// But when we replace "position: relative;" with "postion: inherit", superscript doesn't
|
||||
// But when we replace "position: relative;" with "position: inherit", superscript doesn't
|
||||
// display correctly. We'll check to make sure we don't replace the position in this case.
|
||||
|
||||
int currentOpeningParenthesis = textUpToCapture.LastIndexOf('{');
|
||||
|
|
|
@ -40,6 +40,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
protected IBlogCredentialsAccessor _credentials;
|
||||
protected string _blogHomepageUrl;
|
||||
protected PageDownloader _pageDownloader;
|
||||
|
||||
public BlogPostRegionLocatorStrategy(IBlogClient blogClient, BlogAccount blogAccount, IBlogCredentialsAccessor credentials, string blogHomepageUrl, PageDownloader pageDownloader)
|
||||
{
|
||||
_blogClient = blogClient;
|
||||
|
@ -50,9 +51,12 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
}
|
||||
|
||||
public abstract void PrepareRegions(IProgressHost progress);
|
||||
public abstract BlogPostRegions LocateRegionsOnUIThread(IProgressHost progress);
|
||||
public virtual void FetchTemporaryPostPage(IProgressHost progress, string url) { }
|
||||
public abstract BlogPostRegions LocateRegionsOnUIThread(IProgressHost progress, string pageUrl);
|
||||
public abstract void CleanupRegions(IProgressHost progress);
|
||||
|
||||
public virtual bool CanRefetchPage => false;
|
||||
|
||||
protected void CheckCancelRequested(IProgressHost progress)
|
||||
{
|
||||
if (progress.CancelRequested)
|
||||
|
@ -69,9 +73,11 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
internal class TemporaryPostRegionLocatorStrategy : BlogPostRegionLocatorStrategy
|
||||
{
|
||||
BlogPost temporaryPost;
|
||||
Stream blogHomepageContents;
|
||||
Stream blogPageContents;
|
||||
BlogPostRegionLocatorBooleanCallback containsBlogPosts;
|
||||
|
||||
public override bool CanRefetchPage => true;
|
||||
|
||||
private const string TEMPORARY_POST_STABLE_GUID = "3bfe001a-32de-4114-a6b4-4005b770f6d7";
|
||||
private string TEMPORARY_POST_BODY_GUID = Guid.NewGuid().ToString();
|
||||
private string TEMPORARY_POST_TITLE_GUID = Guid.NewGuid().ToString();
|
||||
|
@ -112,27 +118,36 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
// Publish a temporary post so that we can examine HTML that will surround posts created with the editor
|
||||
temporaryPost = PostTemplate(new ProgressTick(progress, 25, 100));
|
||||
CheckCancelRequested(progress);
|
||||
FetchTemporaryPostPage(progress, _blogHomepageUrl);
|
||||
}
|
||||
|
||||
blogHomepageContents = new MemoryStream();
|
||||
/// <summary>
|
||||
/// Fetch a blog page from the URL specified and transfer it into blogPageContents
|
||||
/// </summary>
|
||||
/// <param name="progress"></param>
|
||||
/// <param name="url"></param>
|
||||
public override void FetchTemporaryPostPage(IProgressHost progress, string url)
|
||||
{
|
||||
blogPageContents = new MemoryStream();
|
||||
|
||||
// Download the webpage that is contains the temporary blog post
|
||||
// WARNING, DownloadBlogPage uses an MSHTML Document on a non-UI thread...which is a no-no!
|
||||
// its been this way through several betas without problem, so we'll keep it that way for now, but
|
||||
// it needs to be fixed eventually.
|
||||
Stream postHtmlContents = DownloadBlogPage(_blogHomepageUrl, progress);
|
||||
Stream postHtmlContents = DownloadBlogPage(url, progress);
|
||||
CheckCancelRequested(progress);
|
||||
|
||||
using (postHtmlContents)
|
||||
{
|
||||
StreamHelper.Transfer(postHtmlContents, blogHomepageContents);
|
||||
StreamHelper.Transfer(postHtmlContents, blogPageContents);
|
||||
}
|
||||
progress.UpdateProgress(100, 100);
|
||||
}
|
||||
|
||||
public override BlogPostRegions LocateRegionsOnUIThread(IProgressHost progress)
|
||||
public override BlogPostRegions LocateRegionsOnUIThread(IProgressHost progress, string pageUrl)
|
||||
{
|
||||
blogHomepageContents.Seek(0, SeekOrigin.Begin);
|
||||
return ParseBlogPostIntoTemplate(blogHomepageContents, _blogHomepageUrl, progress);
|
||||
blogPageContents.Seek(0, SeekOrigin.Begin);
|
||||
return ParseBlogPostIntoTemplate(blogPageContents, pageUrl, progress);
|
||||
}
|
||||
|
||||
public override void CleanupRegions(IProgressHost progress)
|
||||
|
@ -194,17 +209,17 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a webpage from a blog.
|
||||
/// Downloads a webpage from a blog and searches for TEMPORARY_POST_TITLE_GUID.
|
||||
/// </summary>
|
||||
/// <param name="blogHomepageUrl"></param>
|
||||
/// <param name="blogPageUrl"></param>
|
||||
/// <param name="progress"></param>
|
||||
/// <returns></returns>
|
||||
private Stream DownloadBlogPage(string blogHomepageUrl, IProgressHost progress)
|
||||
/// <returns>Stream containing document which contains TEMPORARY_POST_TITLE_GUID.</returns>
|
||||
private Stream DownloadBlogPage(string blogPageUrl, IProgressHost progress)
|
||||
{
|
||||
ProgressTick tick = new ProgressTick(progress, 50, 100);
|
||||
MemoryStream memStream = new MemoryStream();
|
||||
IHTMLDocument2 doc2 = null;
|
||||
// WinLive 221984: Theme detection timing out intermitantly on WordPress.com
|
||||
// WinLive 221984: Theme detection timing out intermittently on WordPress.com
|
||||
// The temp post *often* takes more than a minute to show up on the blog home page.
|
||||
// The download progress dialog has a cancel button, we'll try a lot before giving up.
|
||||
for (int i = 0; i < 30 && doc2 == null; i++)
|
||||
|
@ -218,14 +233,17 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
// This means we'll try for 5 minutes (10s + 290s = 300s) before we consider the operation timed out.
|
||||
Thread.Sleep(i < 10 ? 1000 : 10000);
|
||||
|
||||
HttpWebResponse resp = _pageDownloader(blogHomepageUrl, 60000);
|
||||
// Add random parameter to URL to bypass cache
|
||||
var urlRandom = UrlHelper.AppendQueryParameters(blogPageUrl, new string[] { Guid.NewGuid().ToString() });
|
||||
|
||||
HttpWebResponse resp = _pageDownloader(urlRandom, 60000);
|
||||
memStream = new MemoryStream();
|
||||
using (Stream respStream = resp.GetResponseStream())
|
||||
StreamHelper.Transfer(respStream, memStream);
|
||||
|
||||
//read in the HTML file and determine if it contains the title element
|
||||
memStream.Seek(0, SeekOrigin.Begin);
|
||||
doc2 = HTMLDocumentHelper.GetHTMLDocumentFromStream(memStream, blogHomepageUrl);
|
||||
doc2 = HTMLDocumentHelper.GetHTMLDocumentFromStream(memStream, urlRandom);
|
||||
if (HTMLDocumentHelper.FindElementContainingText(doc2, TEMPORARY_POST_TITLE_GUID) == null)
|
||||
doc2 = null;
|
||||
}
|
||||
|
@ -302,7 +320,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
{
|
||||
private string _titleText;
|
||||
private string _bodyText;
|
||||
private MemoryStream blogHomepageContents;
|
||||
private MemoryStream blogPageContents;
|
||||
BlogPost mostRecentPost;
|
||||
private int recentPostCount = -1;
|
||||
public RecentPostRegionLocatorStrategy(IBlogClient blogClient, BlogAccount blogAccount,
|
||||
|
@ -339,13 +357,13 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
if (normalizedTitleText.IndexOf(normalizedBodyText, StringComparison.CurrentCulture) != -1) //body text is a subset of the title text
|
||||
throw new ArgumentException("Content text is not unique enough to use for style detection");
|
||||
|
||||
blogHomepageContents = DownloadBlogPage(_blogHomepageUrl, progress);
|
||||
blogPageContents = DownloadBlogPage(_blogHomepageUrl, progress);
|
||||
}
|
||||
|
||||
public override BlogPostRegions LocateRegionsOnUIThread(IProgressHost progress)
|
||||
public override BlogPostRegions LocateRegionsOnUIThread(IProgressHost progress, string pageUrl)
|
||||
{
|
||||
blogHomepageContents.Seek(0, SeekOrigin.Begin);
|
||||
IHTMLDocument2 doc2 = HTMLDocumentHelper.GetHTMLDocumentFromStream(blogHomepageContents, _blogHomepageUrl);
|
||||
blogPageContents.Seek(0, SeekOrigin.Begin);
|
||||
IHTMLDocument2 doc2 = HTMLDocumentHelper.GetHTMLDocumentFromStream(blogPageContents, pageUrl);
|
||||
|
||||
// Ensure that the document is fully loaded.
|
||||
// If it is not fully loaded, then viewing its current style is non-deterministic.
|
||||
|
@ -393,7 +411,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
private static bool IsSmartContent(IHTMLElement element)
|
||||
{
|
||||
// search up the parent heirarchy
|
||||
// search up the parent hierarchy
|
||||
while (element != null)
|
||||
{
|
||||
if (0 == String.Compare(element.tagName, "div", StringComparison.OrdinalIgnoreCase))
|
||||
|
@ -511,10 +529,10 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
public override void CleanupRegions(IProgressHost progress)
|
||||
{
|
||||
if (blogHomepageContents != null)
|
||||
if (blogPageContents != null)
|
||||
{
|
||||
blogHomepageContents.Close();
|
||||
blogHomepageContents = null;
|
||||
blogPageContents.Close();
|
||||
blogPageContents = null;
|
||||
}
|
||||
|
||||
progress.UpdateProgress(100, 100);
|
||||
|
|
|
@ -39,7 +39,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
protected override object DetectBlogService(IProgressHost progressHost)
|
||||
{
|
||||
using (BlogClientUIContextSilentMode uiContextScope = new BlogClientUIContextSilentMode()) //supress prompting for credentials
|
||||
using (BlogClientUIContextSilentMode uiContextScope = new BlogClientUIContextSilentMode()) //suppress prompting for credentials
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -488,7 +488,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
protected override object DetectBlogService(IProgressHost progressHost)
|
||||
{
|
||||
using (BlogClientUIContextSilentMode uiContextScope = new BlogClientUIContextSilentMode()) //supress prompting for credentials
|
||||
using (BlogClientUIContextSilentMode uiContextScope = new BlogClientUIContextSilentMode()) //suppress prompting for credentials
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -604,7 +604,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
35);
|
||||
|
||||
// add settings downloading (note: this operation will be a no-op
|
||||
// in the case where we don't succesfully detect a weblog)
|
||||
// in the case where we don't successfully detect a weblog)
|
||||
AddProgressOperation(
|
||||
new ProgressOperation(DetectWeblogSettings),
|
||||
new ProgressOperationCompleted(DetectWeblogSettingsCompleted),
|
||||
|
@ -915,7 +915,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
private object DetectWeblogSettings(IProgressHost progressHost)
|
||||
{
|
||||
using (BlogClientUIContextSilentMode uiContextScope = new BlogClientUIContextSilentMode()) //supress prompting for credentials
|
||||
using (BlogClientUIContextSilentMode uiContextScope = new BlogClientUIContextSilentMode()) //suppress prompting for credentials
|
||||
{
|
||||
// no-op if we don't have a blog-id to work with
|
||||
if (HostBlogId == String.Empty)
|
||||
|
|
|
@ -166,9 +166,11 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
public object DetectSettings(IProgressHost progressHost)
|
||||
{
|
||||
var canRemoteDetect = CreateBlogClient().RemoteDetectionPossible;
|
||||
|
||||
using (_silentMode ? new BlogClientUIContextSilentMode() : null)
|
||||
{
|
||||
if (IncludeButtons || IncludeOptionOverrides || IncludeImages)
|
||||
if ((IncludeButtons || IncludeOptionOverrides || IncludeImages) && canRemoteDetect)
|
||||
{
|
||||
using (new ProgressContext(progressHost, 40, Res.Get(StringId.ProgressDetectingWeblogSettings)))
|
||||
{
|
||||
|
@ -212,7 +214,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
using (new ProgressContext(progressHost, 40, Res.Get(StringId.ProgressDetectingWeblogCharSet)))
|
||||
{
|
||||
if (IncludeOptionOverrides && IncludeHomePageSettings)
|
||||
if (IncludeOptionOverrides && IncludeHomePageSettings && canRemoteDetect)
|
||||
{
|
||||
DetectHomePageSettings();
|
||||
}
|
||||
|
@ -245,7 +247,7 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
|
||||
// detect favicon (only if requested AND we don't have a PNG already
|
||||
// for the small image size)
|
||||
if (IncludeFavIcon)
|
||||
if (IncludeFavIcon && canRemoteDetect)
|
||||
{
|
||||
using (new ProgressContext(progressHost, 10, Res.Get(StringId.ProgressDetectingWeblogIcon)))
|
||||
{
|
||||
|
@ -291,8 +293,8 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Any setting that is derivaed from the homepage html needs to be in this function. This function is turned
|
||||
/// on and off when detecting blog seetings through the IncludeHomePageSettings. None of these checks will be run
|
||||
/// Any setting that is derived from the homepage html needs to be in this function. This function is turned
|
||||
/// on and off when detecting blog settings through the IncludeHomePageSettings. None of these checks will be run
|
||||
/// if the internet is not active. As each check is made, it does not need to be applied back the _content until the end
|
||||
/// at which time it will write the settings back to the registry.
|
||||
/// </summary>
|
||||
|
|
|
@ -208,9 +208,9 @@ namespace OpenLiveWriter.BlogClient.Detection
|
|||
{
|
||||
Trace.Fail("Exception attempting to read RSD file: " + ex.ToString());
|
||||
|
||||
// don't re-propagate exceptions here becaus we found that TypePad's
|
||||
// don't re-propagate exceptions here because we found that TypePad's
|
||||
// RSD file was returning bogus HTTP crap at the end of the response
|
||||
// and the XML parser cholking on this caused us to fail autodetection
|
||||
// and the XML parser choking on this caused us to fail autodetection
|
||||
}
|
||||
|
||||
// if we got at least one API then return the service description
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<HTML>
|
||||
<HEAD>
|
||||
<META http-equiv="Content-Type" content="text/html; utf-8">
|
||||
<META http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<META http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
|
||||
<LINK href="{0}" type="text/css" rel="stylesheet">
|
||||
</HEAD>
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace OpenLiveWriter.BlogClient
|
|||
bool IsSpacesBlog { get; }
|
||||
bool IsSharePointBlog { get; }
|
||||
bool IsGoogleBloggerBlog { get; }
|
||||
bool IsStaticSiteBlog { get; }
|
||||
|
||||
string HostBlogId { get; }
|
||||
string BlogName { get; }
|
||||
|
|
|
@ -10,29 +10,27 @@
|
|||
</NuGetPackageImportStamp>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Google.Apis, Version=1.21.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.1.21.0\lib\net45\Google.Apis.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Google.Apis, Version=1.39.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.1.39.0\lib\net45\Google.Apis.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Google.Apis.Auth, Version=1.21.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Auth.1.21.0\lib\net45\Google.Apis.Auth.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Google.Apis.Auth, Version=1.39.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Auth.1.39.0\lib\net45\Google.Apis.Auth.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Google.Apis.Auth.PlatformServices, Version=1.21.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Auth.1.21.0\lib\net45\Google.Apis.Auth.PlatformServices.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Google.Apis.Auth.PlatformServices, Version=1.39.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Auth.1.39.0\lib\net45\Google.Apis.Auth.PlatformServices.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Google.Apis.Blogger.v3, Version=1.21.0.111, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Blogger.v3.1.21.0.111\lib\net45\Google.Apis.Blogger.v3.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Google.Apis.Core, Version=1.21.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Core.1.21.0\lib\net45\Google.Apis.Core.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Google.Apis.Core, Version=1.39.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Core.1.39.0\lib\net45\Google.Apis.Core.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Google.Apis.PlatformServices, Version=1.21.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.1.21.0\lib\net45\Google.Apis.PlatformServices.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Google.Apis.Drive.v3, Version=1.39.0.1566, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.Drive.v3.1.39.0.1566\lib\net45\Google.Apis.Drive.v3.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Google.Apis.PlatformServices, Version=1.39.0.0, Culture=neutral, PublicKeyToken=4b01fa6e34db77ab, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Google.Apis.1.39.0\lib\net45\Google.Apis.PlatformServices.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Microsoft.Threading.Tasks, Version=1.0.12.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll</HintPath>
|
||||
|
@ -46,9 +44,8 @@
|
|||
<HintPath>..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json, Version=9.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
<Reference Include="Newtonsoft.Json, Version=10.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.10.0.2\lib\net45\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Data" />
|
||||
|
@ -122,6 +119,9 @@
|
|||
<Project>{906BA039-467B-41AE-B805-BA1B837AB763}</Project>
|
||||
<Package>{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</Package>
|
||||
</ProjectReference>
|
||||
<Reference Include="YamlDotNet, Version=6.0.0.0, Culture=neutral, PublicKeyToken=ec19458f3c15af5e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\YamlDotNet.6.1.1\lib\net45\YamlDotNet.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="Zlib.Portable, Version=1.11.0.0, Culture=neutral, PublicKeyToken=431cba815f6a8b5b, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Zlib.Portable.Signed.1.11.0\lib\portable-net4+sl5+wp8+win8+wpa81+MonoTouch+MonoAndroid\Zlib.Portable.dll</HintPath>
|
||||
<Private>True</Private>
|
||||
|
@ -163,7 +163,16 @@
|
|||
<Compile Include="Clients\MovableTypeClient.cs" />
|
||||
<Compile Include="Clients\RedirectHelper.cs" />
|
||||
<Compile Include="Clients\SharePointClient.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSiteConfigDetector.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSiteClient.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSiteConfig.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSiteConfigValidator.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSitePage.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSiteItem.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSiteItemFrontMatter.cs" />
|
||||
<Compile Include="Clients\StaticSite\StaticSitePost.cs" />
|
||||
<Compile Include="Clients\WordPressClient.cs" />
|
||||
<Compile Include="Clients\TistoryBlogClient.cs" />
|
||||
<Compile Include="Clients\XmlRestRequestHelper.cs" />
|
||||
<Compile Include="Clients\XmlRpcBlogClient.cs" />
|
||||
<Compile Include="Detection\BackgroundColorDetector.cs" />
|
||||
|
|
|
@ -23,7 +23,7 @@ namespace OpenLiveWriter.BlogClient.Providers
|
|||
{
|
||||
public BlogClientOptions()
|
||||
{
|
||||
// accept default options (see private data declaratoins for defaults)
|
||||
// accept default options (see private data declarations for defaults)
|
||||
}
|
||||
|
||||
public bool SupportsHttps
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue