Compare commits

...

350 Commits

Author SHA1 Message Date
Gary Ewan Park 5ff6483ef7
Merge pull request #919 from travelmarx/patch-1
Update README.md to point to .com instead of .org
2024-01-14 10:52:37 +00:00
Travelmarx f1fa3c9574
Update README.md to point to .com instead of .org
This is what was already done on OpenLiveWriter.Github.io README.
2020-08-15 11:18:45 +02:00
Nick Vella 8f7f4a7fb6
Merge pull request #876 from ranjanjharavi/fix-691-Space-Between-Pictures
Line between images so that text can be entered
2019-10-19 18:26:07 +11:00
Nick Vella 743df72eed
Merge pull request #875 from ranjanjharavi/fix-303-New-Window-Opened
New window is opened when Single window is selected in preferences
2019-10-19 18:15:51 +11:00
Ravi Ranjan 5324614c21 Add <p> tag below images so that text can be entered 2019-10-17 14:46:00 +01:00
Ravi Ranjan 58b069cadd Fix for new window is opened when Single window is selected in preferences 2019-10-16 12:09:25 +01:00
Nick Vella 75bdea0a97
Merge pull request #873 from ranjanjharavi/fix-820-ShortcutKey-Show-Wrong-focus
Enable focus on the item count box
2019-10-14 12:41:25 +11:00
Ravi Ranjan cbb541a7be added a KeyEventhandler to handle whenever ALT + S is pressed. 2019-10-12 15:00:06 +05:30
Nick Vella 7f18b65c7a
Merge pull request #871 from ranjanjharavi/fix-849-Insert-From-Web-YoutubeURL
Insert from web - allowing YouTube URLs
2019-10-12 18:25:55 +11:00
Ravi Ranjan 453d39cce8 removed query parameter as it is not working with Google Blogger. 2019-10-11 14:02:42 +01:00
Ravi Ranjan b4b9d70462 removed <object> element 2019-10-11 09:03:05 +01:00
Ravi Ranjan 94c29fdcfe Changes made after review to make code cleaner and used <iframe> instead of <embed> as per youtube standards along with using HTML5 syntax instead of HTML4. 2019-10-10 12:38:32 +01:00
Ravi Ranjan dc49d8042b added <head> and <meta> tags along with the meta attributes(content, http-equiv)
removed the shockwave mime type from the embed tags (to take default HTML5 player)
replaced /v/ with /embed/ in the Uri which has to be embedded, in order to preview and play youtube video in Insert Video from Web tab.
2019-10-04 14:41:07 +01:00
Ravi Ranjan 134ccf1bf9 modified regex to allow https and youtu.be URLs 2019-10-04 14:21:16 +01:00
Nick Vella eadf4601ba
Merge pull request #864 from caiovosilva/add-yamlDotNet-copyright
#853 Add YamlDotNet copyright to About dialog
2019-09-27 21:27:37 +10:00
Caio Silva e628623e2a Add YamlDotNet copyright to About dialog
issue #853
2019-09-26 16:55:38 -03:00
Nick Vella 09f1f05c76
Merge pull request #852 from nvella/master
ssg: Add YamlDotNet to nuspec
2019-08-18 16:41:00 +10:00
Nick Vella f3c6a5ff69 ssg: ... tabs->spaces 2019-08-18 16:34:13 +10:00
Nick Vella e16f96ee22 ssg: update nuspec to include YamlDotNet 2019-08-18 16:29:04 +10:00
Nick Vella dbbfd4f4ce
Merge pull request #844 from nvella/static-site-generator
Initial Static Site Generator support
2019-08-18 16:11:13 +10:00
Nick Vella b34cca1ff1
Merge pull request #850 from nvella/master
New splash screen (closes #846)
2019-08-17 18:26:20 +10:00
Nick Vella 857029dbee splash: remove unused label click handler 2019-08-17 18:07:53 +10:00
Nick Vella 462c13a0d6 splash: cleanup ApplicationMain 2019-08-16 23:54:35 +10:00
Nick Vella 99458d7dbe splash: revert accidental change to ribbon project file 2019-08-16 23:51:41 +10:00
Nick Vella b6c751399f splash: rename images to state 2x scaling 2019-08-16 23:50:12 +10:00
Nick Vella c078072471 splash: cleanup files 2019-08-16 23:47:00 +10:00
Nick Vella d22332a054 splash: finish new splash 2019-08-16 23:45:59 +10:00
Nick Vella e543266caa splash: begin work on new splash 2019-08-16 00:07:04 +10:00
Nick Vella c7bb28dbc9
Merge pull request #847 from nvella/master
UpdateManager: don't update if update is older than currently running version (fixes #838)
2019-08-08 00:23:38 +10:00
Nick Vella 654465e576 UpdateManager: check there is a future release before comparing release versions 2019-08-08 00:16:19 +10:00
Nick Vella c1591d8873 UpdateManager: don't update if update is older than currently running version - fixes #838 2019-08-07 23:57:04 +10:00
Nick Vella 5d7077e6c3 ssg: StaticSiteItem: fix parsing blog posts with only one HTML line 2019-08-07 18:51:21 +10:00
Nick Vella 9db6fdd6e9 ssg: oops, forgot drafts support 2019-08-05 22:30:36 +10:00
Nick Vella bcac35626d l10n: restore Properties.resx to upstream 2019-08-05 17:50:07 +10:00
Nick Vella b5fbf0367b l10n: match Strings.csv to Strings.Resx as per PR #213 2019-08-05 17:15:46 +10:00
Nick Vella 78c68a5367 l10n: match Strings.csv to ad-hoc patches made on StringId.cs and Strings.resx 2019-08-05 17:03:28 +10:00
Nick Vella 037d2682f1 l10n: restore comment regarding @kathweaver's fixes to issue #337 2019-08-05 16:53:29 +10:00
Nick Vella 1fe0228189 LocUtil: more enum gen fixes 2019-08-05 16:48:23 +10:00
Nick Vella 620b3e6ac0 LocUtil: more enum gen fix 2019-08-05 16:40:15 +10:00
Nick Vella fbe23bbaa5 LocUtil: fix resx file tag indenting 2019-08-05 16:36:08 +10:00
Nick Vella 154a4f1495 LocUtil: proper formatting of StringId.cs 2019-08-05 16:16:31 +10:00
Nick Vella de7b153364 ssg: advanced config build publish: use strings 2019-08-05 15:57:39 +10:00
Nick Vella bf1e8adb6f ssg: advanced config front matter: use strings 2019-08-04 19:12:18 +10:00
Nick Vella 232708a3f3 ssg: advanced config authoring: use strings 2019-08-04 17:27:32 +10:00
Nick Vella 7706b85796 ssg: switch string literals to string resources 1/... 2019-08-04 02:05:38 +10:00
Nick Vella 8ca2693615 ssg: strings: remove service name 2019-08-04 01:11:22 +10:00
Nick Vella 6c72d1d051 ssg: strings: add missing strings 2019-08-04 01:07:23 +10:00
Nick Vella 3e0acdfb1a ssg: advanced config build publish: fix sizing 2019-08-04 01:07:10 +10:00
Nick Vella 662b9cf0d6 LocEdit: don't exclude null comments 2019-08-04 00:21:28 +10:00
Nick Vella 91d3479839 LocEdit: kb shortcuts and convenience buttons 2019-08-03 23:21:13 +10:00
Nick Vella dd95e6e4fb ssg: strings: update CWStaticSiteInitialSubtitleAlreadyDetected message 2019-08-03 23:07:13 +10:00
Nick Vella dab97c1e57 l10n: reformat Strings.csv with LocEdit, resultant StringId.cs and Strings.resx have been regenerated with minimal changes 2019-08-03 23:04:25 +10:00
Nick Vella 025a49f1f5 ssg: strings: remove PostUrlFormat string from StringId and resources 2019-08-03 22:53:55 +10:00
Nick Vella 4451d96ba9 LocEdit: use default encoding 2019-08-03 22:52:56 +10:00
Nick Vella 88b018607b LocEdit: finish implementation 2019-08-03 22:44:46 +10:00
Nick Vella 06289e19d0 LocEdit wip 2019-08-03 21:19:10 +10:00
Nick Vella 6f524388c7 ssg: strings: remove PostUrlFormat 2019-08-03 21:18:42 +10:00
Nick Vella a64ae677a0 ssg: remove PostUrlFormat. this field is currently not required, and can be a potential cause for end-user confusion 2019-08-02 22:36:41 +10:00
Nick Vella 27e979f19a ssg: advanced settings front matter: reset to defaults button 2019-08-02 22:00:50 +10:00
Nick Vella 1fc8ac670f ssg: advanced settings: fix designer and tab indexes 2019-08-02 14:57:38 +10:00
Nick Vella 753116f329 ssg: advanced config StaticSitePreferencesPanel: make non abstract as designer can't deal with abstract classes 2019-08-02 14:24:18 +10:00
Nick Vella 1cc80f7b0d test: ssg: StaticSiteItemFrontMatterTests: fix instantiations of StaticSiteItemFrontMatter 2019-08-02 14:10:19 +10:00
Nick Vella 406a63cc82 ssg: advanced config saving 2019-08-02 02:03:03 +10:00
Nick Vella 3a1502dd4f ssg: update PostEditor csproj 2019-08-01 23:59:57 +10:00
Nick Vella 8d9f6f9204 ssg: advanced config rename references to StaticSitePreferencesController 2019-08-01 23:57:09 +10:00
Nick Vella 872961ee0a ssg: advanced config: rename PreferencesController to StaticSitePrefrencesController, begin work on saving 2019-08-01 23:54:23 +10:00
Nick Vella b1cbb5c6ad ssg: advanced config: impl StaticSitePreferencesPanel with common protected attributes, make all panels inherit it 2019-08-01 23:53:13 +10:00
Nick Vella b7751ca027 ssg: advanced config FrontMatterPanel: add getter for FrontMatterKeys 2019-08-01 23:52:18 +10:00
Nick Vella e28f2e64c4 ssg: StaticSiteClient: allow for disabled timeouts 2019-07-31 20:51:35 +10:00
Nick Vella 8143dcf231 ssg: StaticSiteConfig: load and save FrontMatterKeys, include in clone 2019-07-30 17:57:33 +10:00
Nick Vella 318fd847f4 ssg: StaticSiteConfig: remove SSG prefix from credentials keys 2019-07-30 17:43:21 +10:00
Nick Vella 0582307be9 ssg: StaticSiteConfigFrontMatterKeys: load and save from blog credentials 2019-07-30 17:42:47 +10:00
Nick Vella e2fd2bffb9 ssg: StaticSiteItemFrontMatter: take StaticSiteConfigFrontMatterKeys in constructor, adjust static methods to suit 2019-07-30 17:12:11 +10:00
Nick Vella 7622f29124 ssg: StaticSiteConfigFrontMatterKeys: default values 2019-07-30 17:11:24 +10:00
Nick Vella a6b7b81cb6 ssg: StaticSiteConfigValidator impl, move validation from wizard panels 2019-07-29 23:37:37 +10:00
Nick Vella 6de3348b88 ssg: StaticSiteConfig: Clone impl 2019-07-29 23:36:32 +10:00
Nick Vella e8af31e41c ssg: advanced config BuildPublishPanel: attributes, loading 2019-07-29 16:19:04 +10:00
Nick Vella 506e9a3638 ssg: advanced config BuildPublishPanel: recompute checkbox states on change 2019-07-29 15:50:51 +10:00
Nick Vella 2f9749b8a8 dpi: SideBarControl: scale text-only vertical padding 2019-07-26 02:30:18 +10:00
Nick Vella 983c7b8389 ssg: advanced config BuildPublishPanel layout 2019-07-26 02:25:59 +10:00
Nick Vella 83c76f4a72 ssg: advanced config AuthoringPanel: only naturalize height, improve control alignment 2019-07-26 01:22:45 +10:00
Nick Vella 46199f9ea6 ssg: advanced config FrontMatterPanel: generate table from StaticSiteConfigFrontMatterKeys in class, scale to DPI on layout 2019-07-26 01:11:56 +10:00
Nick Vella 68d9c977d7 ssg: advanced config Front Matter panel 2019-07-25 01:36:13 +10:00
Nick Vella 0f386718e4 ssg: advanced configuration Authoring panel 2019-07-25 00:40:08 +10:00
Nick Vella 01e7e63abe ssg: advanced settings General panel: select folder button for local site path 2019-07-24 22:52:38 +10:00
Nick Vella bd334dccf3 ssg: advanced settings General panel: fix dpi scaling related overflow 2019-07-24 22:26:55 +10:00
Nick Vella 4750b05edd ssg: advanced config: use preferences form, add basic general panel, relevant controller logic 2019-07-24 01:41:30 +10:00
Nick Vella 6e2db9bf42 SideBarControl: add padding if there is no image for button 2019-07-24 01:39:27 +10:00
Nick Vella bb6d3bfa1d ssg: begin work on advanced config 2019-07-22 20:00:54 +10:00
Nick Vella 89fbbc1a3c ssg: wizard: WeblogConfigurationWizardController: perform template autodetection on static site wizard completion 2019-07-22 18:57:58 +10:00
Nick Vella 29d4ac314c ssg: StaticSiteClient: enable template support 2019-07-22 02:10:36 +10:00
Nick Vella c6952ab4e8 blog template detection: if post body is not found on homepage, attempt to template off of post 2019-07-22 02:10:24 +10:00
Nick Vella 4e35f56a55 ssg: HttpRequestHelper: never cache http requests 2019-07-22 02:08:05 +10:00
Nick Vella b8def8bbb7 ssg: StaticSiteClient: image uploading 2019-07-21 19:05:57 +10:00
Nick Vella 9965452a5a ssg: StaticSiteConfig: SiteUrl config key, store copy of site url in credentials so that it can be accessed by StaticSiteClient after instantiation 2019-07-21 19:05:42 +10:00
Nick Vella 71c8ca6edb ssg: StaticSiteClient: DeletePage impl 2019-07-21 02:00:14 +10:00
Nick Vella 08ac515ec2 ssg: StaticSiteClient: DoDeleteItem impl 2019-07-21 02:00:05 +10:00
Nick Vella f18011d75e ssg: StaticSitePost: use DatePublished from item for GetFileNameForProvidedSlug 2019-07-21 01:32:10 +10:00
Nick Vella cbaaa0f811 ssg: StaticSiteClient: EditPage impl 2019-07-21 01:31:35 +10:00
Nick Vella 8f098815d5 ssg: StaticSiteClient: DoEditItem method impl for generic item editing 2019-07-21 01:31:22 +10:00
Nick Vella 6a67ca4579 ssg: StaticSitePage: GetParentSlugs impl, max levels for parent crawl 2019-07-20 22:51:50 +10:00
Nick Vella 96606c3d0f ssg: StaticSitePage: GetFileNameForProvidedSlug include parent slugs 2019-07-20 22:27:03 +10:00
Nick Vella 894a05e1a9 ssg: StaticSiteItem, StaticSitePost: make GetSlugFromPublishFileName abstract 2019-07-20 22:16:53 +10:00
Nick Vella a314de778c ssg: StaticSitePage: SitePath override impl 2019-07-20 21:03:52 +10:00
Nick Vella 9ee13c5a59 ssg: StaticSitePage, StaticSiteClient: ResolveParent impl, resolve page parent on GetPage 2019-07-20 00:43:21 +10:00
Nick Vella 923acc40ad ssg: StaticSiteItemFrontMatter: add ParentId key 2019-07-20 00:42:18 +10:00
Nick Vella 1493078a2a ssg: StaticSiteClient: page get and list 2019-07-19 23:38:28 +10:00
Nick Vella 4ee27ca0c0 ssg: StaticSiteClient: return null on post not found, rather than throwing exception 2019-07-19 23:30:55 +10:00
Nick Vella 45196f5448 ssg: StaticSitePage impl 2019-07-19 21:01:28 +10:00
Nick Vella 5ee2844533 ssg: StaticSiteItem, StaticSitePost: make SitePath abstract 2019-07-19 21:00:52 +10:00
Nick Vella cda9909d27 docs: ssg: fix typos 2019-07-19 20:15:22 +10:00
Nick Vella e34474d996 docs: ssg: update docs for new classes 2019-07-19 20:11:20 +10:00
Nick Vella 844881a840 tests: ssg: rename ssg test files 2019-07-19 17:38:22 +10:00
Nick Vella 7af5d3b9a5 ssg: introduce abstract StaticSiteItem, impl StaticSitePost to inherit and override StaticSiteItem 2019-07-19 17:37:35 +10:00
Nick Vella 9a3e742945 ssg: StaticSiteClient: basic pages impl 2019-07-19 17:05:23 +10:00
Nick Vella 409e991101 ssg: StaticSitePage impl 2019-07-19 17:05:04 +10:00
Nick Vella 1812bf2761 ssg: StaticSitePost: set DatePublishedOverride on date set, use UtcNow for ensure date 2019-07-19 16:19:42 +10:00
Nick Vella 66e69f30bd ssg: StaticSiteClient: support extended entries 2019-07-19 16:19:20 +10:00
Nick Vella aed86f9b3b ssg: StaticSitePostFrontMatter: remove timezone conversions, OLW deals in UTC internally 2019-07-19 15:43:34 +10:00
Nick Vella f51cc368be ssg: StaticSiteClient: DeletePost impl 2019-07-19 01:48:16 +10:00
Nick Vella a09c738141 ssg: StaticSiteClient: command timeout 2019-07-19 01:48:06 +10:00
Nick Vella 1c7c774795 ssg: StaticSiteConfig: command timeout setting 2019-07-19 01:47:39 +10:00
Nick Vella ea9eaf32f9 ssg: StaticSitePost: set FilePathById on post load from file 2019-07-19 01:47:26 +10:00
Nick Vella 51a001841d ssg: StaticSitePostFrontMatter: set DatePublishedOverride on SaveToBlogPost to make date posted visible in UI 2019-07-19 01:27:19 +10:00
Nick Vella b9287e65a4 ssg: StaticSiteClient: EditPost impl 2019-07-19 01:13:50 +10:00
Nick Vella b10e85e829 ssg: StaticSitePost: the ?? operator didn't do what I thought it did 2019-07-19 01:13:37 +10:00
Nick Vella a00b06dd1b ssg: StaticSitePost: FILENAME_SLUG_REGEX include full paths 2019-07-19 01:13:11 +10:00
Nick Vella 4be51d4646 ssg: StaticSitePost: add DiskSlug attribute 2019-07-19 01:12:50 +10:00
Nick Vella 85defa0b32 ssg: StaticSitePost: GetNewSlug -> FindNewSlug 2019-07-19 00:22:04 +10:00
Nick Vella 0768c839b5 ssg: StaticSitePostFrontMatter: convert on-disk UTC time to local time 2019-07-18 22:24:39 +10:00
Nick Vella 3a71cea99f ssg: StaticSitePost: FilePathById impl, SaveToFile postFilePath parameter 2019-07-18 22:12:04 +10:00
Nick Vella 48564482b5 ssg: StaticSiteClient: ShowCmdWindows config impl, custom handler for hidden window, fixes git hang when output is not being read 2019-07-18 22:10:57 +10:00
Nick Vella 0c8290e4c8 ssg: StaticSiteConfig: ShowCmdWindows flag for debugging 2019-07-18 17:12:22 +10:00
Nick Vella 8359290301 ssg: StaticSitePost: rename GetNewSafeSlug to GetNewSlug, add safe parameter 2019-07-16 23:25:54 +10:00
Nick Vella 0e36c274e0 ssg: StaticSitePostFrontMatter: load NewCategories into tags as well as Categories 2019-07-16 19:23:44 +10:00
Nick Vella 103d47b376 ssg: StaticSitePostFrontMatter: olw_id => id 2019-07-16 19:23:11 +10:00
Nick Vella 8dc9bbac90 ssg: StaticSiteClient: GetCategories impl 2019-07-16 17:59:34 +10:00
Nick Vella 2dcbb3553e ssg: StaticSiteClient: impl GetPost 2019-07-16 15:17:36 +10:00
Nick Vella ba971fc2db ssg: StaticSiteClient: GetRecentPosts use StaticSitePost.GetAllPosts 2019-07-16 15:17:15 +10:00
Nick Vella 59808f70da ssg: StaticSitePost: impl GetAllPosts, GetPostById 2019-07-16 15:16:57 +10:00
Nick Vella 4623753bd8 ssg: StaticSiteClient: order and limit recent posts 2019-07-16 15:01:14 +10:00
Nick Vella 0d84ae9927 ssg: StaticSiteClient: GetRecentPosts fix date logic 2019-07-15 20:55:58 +10:00
Nick Vella acc8eba6c2 ssg: StaticSitePost: LoadFromFile impl 2019-07-15 20:55:27 +10:00
Nick Vella d2a963ed1d ssg: ...: use SaveToFile instead of SaveToDisk to keep consistent 2019-07-15 16:54:45 +10:00
Nick Vella 60ec2275b6 ssg: StaticSitePost: stub LoadFromFile instance method, impl static method 2019-07-15 16:54:03 +10:00
Nick Vella cc61210ad9 ssg: StaticSitePost: add constructor for post-less instantiation 2019-07-15 16:53:45 +10:00
Nick Vella b81daeb4e0 ssg: StaticSiteClient: GetRecentPosts impl 2019-07-15 16:53:07 +10:00
Nick Vella 38c799388e ssg: StaticSiteClient: tidy code formatting 2019-07-15 16:51:54 +10:00
Nick Vella dc19762c2e ssg: StaticSiteClient: reword post as draft error trace message 2019-07-15 01:16:07 +10:00
Nick Vella 76f7ec83b9 ssg: StaticSitePost, StaticSiteClient: impl EnsureDate, use in StaticSiteClient NewPost 2019-07-15 00:31:16 +10:00
Nick Vella 21e987b52e docs: ssg: basic docs on static site gen classes, UML class diagram 2019-07-15 00:16:44 +10:00
Nick Vella 4ad1e89349 ssg: StaticSiteClient, StaticSitePost: move EnsureId call to StaticSiteClient from StaticSitePost SaveToDisk, return postId from NewPost 2019-07-14 22:19:45 +10:00
Nick Vella 9cd3fbf953 ssg: StaticSitePost: use Id when an available slug can't be found 2019-07-14 19:09:16 +10:00
Nick Vella 689fee79e5 ssg: StaticSitePost: ensure Id and safe slug when saving to disk 2019-07-14 19:08:17 +10:00
Nick Vella e614030d46 ssg: StaticSitePost: Id, impl EnsureId, Slug set to BlogPost.Slug and _safeSlug, change EnsureSafeSlug to return string 2019-07-14 19:07:51 +10:00
Nick Vella 48a5f68b08 ssg: StaticSiteFrontMatterKeys: make struct, add Id 2019-07-14 18:46:22 +10:00
Nick Vella 6a2c958be9 ssg: StaticSitePostFrontMatter: add Id 2019-07-14 18:45:58 +10:00
Nick Vella e3a3a28b84 ssg: StaticSitePost: don't mutate BlogPost 2019-07-14 15:11:54 +10:00
Nick Vella 0d87b85402 ssg: StaticSitePost: impl EnsureSafeSlug 2019-07-14 15:10:27 +10:00
Nick Vella 2844c175af ssg: StaticSiteClient: add - as a url safe char 2019-07-14 15:10:11 +10:00
Nick Vella d2a7522ff0 ssg: StaticSitePost: impl GetFileNameForProvidedSlug, GetFilePathForProvidedSlug, ... 2019-07-14 14:51:07 +10:00
Nick Vella d30b2c038d ssg: StaticSitePost: SitePath getter 2019-07-14 00:09:10 +10:00
Nick Vella 0ecb7ae9e1 ssg: StaticSiteClient: use StaticSitePost SaveToDisk in NewPost 2019-07-14 00:01:13 +10:00
Nick Vella 1108c5ee60 ssg: StaticSitePost: slug generation; Slug, FileName, FilePath getters 2019-07-14 00:00:39 +10:00
Nick Vella 645235bd54 ssg: StaticSiteClient, StaticSitePost: StaticSitePost initial impl, switch StaticSiteClient to use StaticSitePost for new posts 2019-07-13 22:36:21 +10:00
Nick Vella 41f6ffabf2 ssg: StaticSitePostFrontMatter: alias ToString to Serialize, add doc comments 2019-07-13 22:35:45 +10:00
Nick Vella 79b801708e ssg: StaticSitePostFrontMatter: fix serialization 2019-07-13 21:43:25 +10:00
Nick Vella d650f0d890 tests: ssg: StaticSitePostFrontMatter: serialization tests 2019-07-13 21:43:09 +10:00
Nick Vella 362190c6f9 ssg: StaticSitePostFrontMatter initial impl 2019-07-12 00:04:29 +10:00
Nick Vella b411a942fa ssg: StaticSiteClient: throw NotImplementedException for GetImageEndpoints 2019-07-12 00:04:06 +10:00
Nick Vella fc1503163a tests: ssg: missing keys test for StaticSitePostFrontMatter#Deserialize 2019-07-12 00:02:48 +10:00
Nick Vella a3d48dbb27 tests: ssg: tests for StaticSitePostFrontMatter#Deserialize 2019-07-12 00:00:11 +10:00
Nick Vella 0ce5352ccb ssg: StaticSiteClient: throw exceptions for adding and suggesting categories, getting image endpoints 2019-07-11 23:02:05 +10:00
Nick Vella dd16678c00 ssg: StaticSiteClient: move PostFrontMatter out 2019-07-11 23:01:36 +10:00
Nick Vella 8eb1f321f2 ssg: StaticSiteClient: don't support authors for now 2019-07-11 22:53:06 +10:00
Nick Vella 0b1ff58439 ssg: StaticSiteConfigFrontMatterKeys definition 2019-07-11 22:20:10 +10:00
Nick Vella 71e601e03f ssg: move StaticSiteClient, StaticSiteConfig, StaticSiteConfigDetector into OpenLiveWriter.BlogClient.Clients.StaticSite namespace 2019-07-11 18:48:33 +10:00
Nick Vella 803312868f ssg: StaticSiteClient: more date fixes 2019-07-10 19:32:47 +10:00
Nick Vella a895605b34 ssg: StaticSiteClient: set post date if not already assigned, assign more frontmatter 2019-07-10 19:07:37 +10:00
Nick Vella ce0fe5b080 ssg: StaticSiteClient: set ClientOptions from StaticSiteConfig 2019-07-10 18:45:27 +10:00
Nick Vella 6711a6eeb6 ssg: use StaticSiteConfigDetector 2019-07-10 18:29:32 +10:00
Nick Vella 6c5341c6dc ssg: StaticSiteConfigDetector: initial impl, detect jekyll 2019-07-10 18:29:13 +10:00
Nick Vella d4692079a3 ssg: StaticSiteConfig: SiteTitle attribute, load from BlogSettings 2019-07-10 18:28:37 +10:00
Nick Vella a7b0b4c61d ssg: wizard: fix distribution in wizard panels 2019-07-09 23:23:45 +10:00
Nick Vella d55f47c65e LayoutHelper: add scaling options, and NoScale versions to distribution helpers 2019-07-09 23:23:24 +10:00
Nick Vella b51341f2f8 ssg: wizard commands: validation 2019-07-08 22:53:21 +10:00
Jon Galloway de565b29c6
Merge pull request #796 from ppardi/Format-HTML-in-Source
Option to always format HTML
2019-07-07 17:19:08 -04:00
Nick Vella 5a1bdb32b0 ssg: wizard paths1: show a more helpful error when posts path is empty 2019-07-08 01:53:29 +10:00
Nick Vella 505ffa4380 ssg: wizard paths1: fixed setting enabled states of drafts and pages labels 2019-07-08 01:52:55 +10:00
Nick Vella 547ae02d2d ssg: wizard commands: commands wizard panel impl, validation still required 2019-07-08 01:51:19 +10:00
Nick Vella e04d675f11 ssg: wizard features: change title 2019-07-07 23:15:08 +10:00
Nick Vella f32565b60d ssg: wizard paths1: move _localSitePath above control definitions 2019-07-07 01:00:30 +10:00
Nick Vella 4cded49426 ssg: wizard paths1: integrate with enabled states in StaticSiteConfig 2019-07-07 00:59:17 +10:00
Nick Vella d015f16728 ssg: wizard paths2: remove leading and trailing slash from output path 2019-07-07 00:58:41 +10:00
Nick Vella a364b74123 ssg: wizard features panel 2019-07-07 00:36:32 +10:00
Nick Vella 21eadf04df ssg: wizard paths2: make consistent in winforms designer 2019-07-07 00:06:42 +10:00
Nick Vella 523be10b2e ssg: wizard paths2: adjust url format validation error 2019-07-06 23:36:30 +10:00
Nick Vella 42b158941d ssg: wizard paths2: url format validation 2019-07-06 23:29:36 +10:00
Nick Vella 1702425387 ssg: wizard paths2: more descriptive error message on empty paths 2019-07-06 23:20:16 +10:00
Nick Vella a36f4859a2 ssg: paths2: forgot to set _buildingEnabled 2019-07-06 23:16:08 +10:00
Nick Vella 25a29272df ssg: wizard: fix ConfigPanelId definitions 2019-07-06 23:14:54 +10:00
Nick Vella 6f7c5cb34c ssg: strings: make some wizard strings consistent 2019-07-06 23:10:34 +10:00
Nick Vella 80f6d25354 ssg: wizard paths2: validation 2019-07-06 23:00:31 +10:00
Nick Vella bd331f238f ssg: wizard paths2: load and save to StaticSiteConfig 2019-07-06 22:48:47 +10:00
Nick Vella 11648544b8 ssg: StaticSiteConfig: add OutputPath setting 2019-07-06 22:48:01 +10:00
Nick Vella 7790e21d4b ssg: StaticSiteConfig: make sure to pass through default value for PostUrlFormat 2019-07-06 22:05:51 +10:00
Nick Vella 65819885ab ssg: StaticSiteConfig: fix config keys 2019-07-06 22:05:17 +10:00
Nick Vella a91bb7c27b ssg: StaticSiteConfig: set PostUrlFormat default 2019-07-06 21:48:26 +10:00
Nick Vella 811f809c03 ssg: wizard initial: remove resource reference inserted by designer 2019-07-06 21:46:14 +10:00
Nick Vella 3ce2e21d2d ssg: wizard paths2: use a grey consistent with other wizard panels 2019-07-06 21:45:45 +10:00
Nick Vella de3c157798 ssg: strings: adjust CWStaticSitePathsUrlFormatSubtitle as a custom format string syntax will be used. 2019-07-06 21:45:18 +10:00
Nick Vella e91c110614 ssg: StaticSiteConfig: add enabled flags for building, pages, and drafts. add fields and enable flags for images. add post url format field. 2019-07-06 21:44:28 +10:00
Jon Galloway afb7f965ff
Merge pull request #833 from nvella/fix-828-new-page-dropdown
Make Page Parent drop-down fill horizontal space (fixes #828)
2019-07-05 14:15:59 -04:00
Jon Galloway 8bae8eb6f9
Merge pull request #832 from nvella/fix-629-split-post-indicator
Fix post split indicator rendering issues, make DPI aware (fixes #629)
2019-07-05 14:15:03 -04:00
Jon Galloway 008ab86361
Merge pull request #830 from nvella/fix-829-mshtml-behaviors
Set MSHTML IE9 Emulation Mode (fixes #829 and others)
2019-07-05 14:14:09 -04:00
Nick Vella 63a0ff881d ssg: strings: update post url format hint 2019-07-06 00:31:38 +10:00
Nick Vella e0dc086348 ssg; wizard panel Paths2: implement layout 2019-07-06 00:28:07 +10:00
Nick Vella cfc8143d6d ssg: WizardController: integrate Paths2 panel 2019-07-05 22:10:16 +10:00
Nick Vella 26a9b06229 ssg: add strings for new fields 2019-07-05 22:05:53 +10:00
Nick Vella 3f823e01a3 ssg: wizard Paths1: reorg private definitions 2019-07-04 21:18:49 +10:00
Nick Vella 67eae75aa7 ssg: wizard: duplicate Paths1 -> Paths2 2019-07-04 21:18:37 +10:00
Nick Vella 6bb325d36c ssg: rename Paths -> Paths1 2019-07-04 21:04:38 +10:00
Nick Vella 5c71216067 ssg: WizardPanelStaticSiteInitial,Paths: remove unused methods 2019-07-04 21:01:05 +10:00
Nick Vella 9d5946fb0a ssg: accidentally commited updated ribbon project platform, revert 2019-07-04 18:09:50 +10:00
Nick Vella 82eec4d55e ssg: WizardPanelStaticSitePaths: paths first page validation 2019-07-04 18:05:30 +10:00
Nick Vella 0f5a6c9755 ssg: WeblogConfigurationWizardController: load config from blog settings, save homepage url 2019-07-04 00:49:58 +10:00
Nick Vella dd063fc85f ssg: StaticSiteConfig: drafts path and site url, site url loaded from BlogSettings, saving handled externally
ssg: StaticSiteConfig: impl LoadFromBlogSettings, static LoadConfigFromBlogSettings
2019-07-04 00:49:10 +10:00
Nick Vella c95181bf21 ssg: WizardPanelStaticSitePaths: inputs for site url, posts, pages, drafts, checkboxes for enabling and disabling pages and drafts, and setting pages stored in root 2019-07-04 00:41:16 +10:00
Nick Vella 2cb98a94f1 ssg: strings: add strings for WizardPanelStaticSitePaths 2019-07-03 23:56:45 +10:00
Nick Vella 9c30545d38 ssg: WizardPanelStaticSitePaths: beginning of static site paths 2019-07-03 19:59:05 +10:00
Nick Vella 2965b2a65a ssg: WeblogConfigurationWizardController: support for multi-page ssg config by persisting StaticSiteConfig object in wizard controller 2019-07-03 19:58:50 +10:00
Nick Vella 8ca41ac199 ssg: WizardPanelStaticSiteInitial: change subtitle message if already initialised 2019-07-03 19:57:43 +10:00
Nick Vella aadb13fc59 ssg: StaticSiteConfig: Initialised variable, records if config detection has been attempted 2019-07-03 19:57:09 +10:00
Nick Vella f1d16cc3e3 ssg: strings: Add strings for config detection 2019-07-03 19:56:29 +10:00
Nick Vella 4cf28cdb04 ssg: wizard: begin multistep wizard, impl step 1 - initial 2019-07-03 18:18:01 +10:00
Nick Vella 22277ed2f9 ssg: ...: use StaticSiteConfig 2019-07-03 16:05:55 +10:00
Nick Vella b7b0c6b577 ssg: StaticSiteConfig: create 2019-07-03 16:05:42 +10:00
Nick Vella 40b92cd742 ssg: StaticSiteClient: update csproj 2019-07-03 16:05:19 +10:00
Nick Vella 1dc0c04452 ssg: create Clients/StaticSite folder, move StaticSiteClient into 2019-07-03 14:50:12 +10:00
Nick Vella b8003344d9 ssg: WeblogConfigurationWizardController: allow editing of existing configs 2019-07-03 14:49:15 +10:00
Nick Vella 72b197bdef ssg: StaticSiteClient: delete output file on post failure 2019-07-03 14:24:01 +10:00
Nick Vella 5976ae3b32 ssg: StaticSiteClient: return extension in GetFileNameForPost 2019-07-03 14:23:41 +10:00
Nick Vella b32b6ef47a ssg: StaticSiteClient: run commands with 64bit cmd on 32-on-64 environments 2019-07-03 14:20:33 +10:00
Nick Vella 7c061ba390 ssg: StaticSiteClient: fix product name in error messages 2019-07-03 13:59:36 +10:00
Nick Vella 299c6081d5 ssg: StaticSiteClient: basic building and publishing 2019-07-03 01:05:26 +10:00
Nick Vella c7f36efabd ssg: Strings: build and publish error messages 2019-07-03 01:05:01 +10:00
Nick Vella 6a1c00e6c2 ssg: StaticSiteClient: NewPost basic initial impl 2019-07-02 18:41:11 +10:00
Nick Vella 0125c52198 ssg: StaticSiteClient: GetFileNameForPost initial impl 2019-07-02 18:40:40 +10:00
Nick Vella b0a78d7085 ssg: StaticSiteClient: not implemented exception for SendAuthenticatedHTTPRequest 2019-07-02 18:40:20 +10:00
Nick Vella 7bcaec39d8 ssg: StaticSiteClient: tidy post front matter class and generation logic 2019-07-02 18:39:41 +10:00
Nick Vella d692a2862d ssg: StaticSiteClient: set SupportsPages client option based on if we were passed a pages path, set SupportsPostAsDraft to false 2019-07-02 17:28:25 +10:00
Nick Vella 7cb8954bb0 ssg: IBlogClient, BlogClientBase, BlogSettingsDetector, ...: BlogClient RemoteDetection Possible attribute, return true for all clients except StaticSiteClient 2019-07-02 17:27:29 +10:00
Nick Vella 93c480dca0 ssg: StaticSiteClient: rename file from StaticSiteGeneratorClient.cs, add constants 2019-07-01 22:51:41 +10:00
Nick Vella 75bbefe40d ssg: WeblogConfigurationWizardPanelStaticSiteConfig: implement IAccountBasicInfoProvider, return a Credentials with site configuration 2019-07-01 22:51:01 +10:00
Nick Vella 7323cc6e9e ssg: WeblogConfigurationWizardController: set blog provider, client and service type from constants, set HomepageUrl and Credentials from SSG wizard panel 2019-07-01 22:38:41 +10:00
Nick Vella 24f08e63ea ssg: rename StaticSiteGeneratorClient.cs 2019-07-01 22:35:54 +10:00
Nick Vella 7d02022ed1 ssg: WeblogConfigurationWizardPanelStaticSiteConfig: add basic validation 2019-07-01 20:09:02 +10:00
Nick Vella b4b14cac51 l10n properties: Add messages for SSG. I used VS to edit this file, hence the large commit 2019-07-01 20:08:24 +10:00
Nick Vella 48cc98e125 PathHelper: RemoveLeadingAndTrailingSlash impl 2019-07-01 20:07:13 +10:00
Nick Vella ad39d20de7 ssg: WeblogConfigurationWizardPanelStaticSiteConfig: add button for folder browser 2019-07-01 19:02:41 +10:00
Nick Vella 0c30378a40 l10n strings: fix missing strings and inconsistencies in Strings.csv 2019-07-01 19:01:22 +10:00
Nick Vella aff3242dd3 l10n strings: re-add UnexpectedErrorSendError, as it was never added to Strings.csv 2019-07-01 18:44:17 +10:00
Nick Vella e07613a00d buildstrings.cmd script for easy rebuilding of strings tables 2019-07-01 18:42:05 +10:00
Nick Vella e06c8234a8 LocUtil: add notice for auto-generated files, rebuild 2019-07-01 18:41:49 +10:00
Nick Vella 6119d37dd5 Strings: rebuild with LocUtil 2019-07-01 18:28:49 +10:00
Nick Vella 21e72c5f4f LocUtil: allow building only strings tables 2019-07-01 18:28:30 +10:00
Nick Vella 81ab755777 ssg: begin and integrate WeblogConfigurationWizardPanelStaticSiteConfig 2019-06-30 22:23:14 +10:00
Nick Vella 9742077bc5 ssg: BlogClientManager: register StaticSiteClient 2019-06-30 22:22:22 +10:00
Nick Vella 36689de1ac ssg: TemporaryBlogSettings: add IsStaticSite setting 2019-06-30 22:22:07 +10:00
Nick Vella ff4663ec9d ssg: WeblogConfigurationWizardPanelBlogType: add 'Static Site Generator' option to wizard 2019-06-30 22:21:32 +10:00
Nick Vella 005cac2846 ssg: BlogSettings, IBlogSettingsAccessor: add IsStaticSite setting 2019-06-30 22:20:42 +10:00
Nick Vella b6ac711cde ssg: Strings: add wizard page title 2019-06-30 22:20:22 +10:00
Nick Vella db98134f8d ssg: add strings 2019-06-30 22:12:11 +10:00
Nick Vella 2365a9b949 ssg: Rename StaticSiteGeneratorClient to StaticSiteClient 2019-06-30 22:11:53 +10:00
Nick Vella 61275bcbdf ssg: StaticSiteGeneratorClient: stub from IBlogClient 2019-06-30 18:17:35 +10:00
Nick Vella d0b62a6b94 ssg: StaticSiteGeneratorClient: add more docs 2019-06-30 17:54:13 +10:00
Nick Vella 96242f2f60 ssg: basic front matter structure and YAML serialization 2019-06-30 17:53:38 +10:00
Nick Vella 651ff1575f ssg: add YamlDotNet package 2019-06-30 17:52:42 +10:00
Nick Vella 0f69b7f4b4 ssg: Start on StaticSiteGeneratorClient 2019-06-30 15:59:03 +10:00
Nick Vella 571297d881 page parent dropdown: PostPropertiesBandControl: designer- VS form designer changed all the location and sizing constants, revert them (2) 2019-06-30 13:56:04 +10:00
Nick Vella 8bd487fe13 page parent dropdown: PostPropertiesBandControl: designer- VS form designer changed all the location and sizing constants, revert them 2019-06-30 13:50:17 +10:00
Nick Vella ae72c81ac8 page parent dropdown: PostPropertiesBandControl: force page parent dropdown to vertically align with publish date picker 2019-06-30 13:39:02 +10:00
Nick Vella a51a71c491 page parent dropdown: PostPropertiesBandControl: dynamically adjust page parent dropdown column sizing and visibility 2019-06-30 13:20:41 +10:00
Nick Vella ab6bc03a26 page parent dropdown: PostPropertiesBandControl: designer- anchor comboPageParent to left and right sides 2019-06-30 13:18:11 +10:00
Nick Vella e1a622f46e split post: SplitterControl: tile splitter separator image to 255 pixels wide 2019-06-29 22:57:09 +10:00
Nick Vella 4fe0582645 split post: ExtendedEntrySplitterElementBehavior, SplitterControl: make DPI aware 2019-06-29 22:52:23 +10:00
Nick Vella 6b8d3d1fe8 DisplayHelper: scaling functions which automatically run Math.Ceiling 2019-06-29 22:51:09 +10:00
Nick Vella 3eb2ad9513 split post: ExtendedEntrySplitterElementBehavior, SplitterControl: set static splitter height of 16, refactor SplitterControl height code, pass SplitterControl height to constructor from ExtendedEntrySplitterElementBehavior, and compute splitter div height based on static splitter height. SplitterControl will no longer dynamically resize based on font size, but should not be an issue. 2019-06-29 22:00:28 +10:00
Nick Vella e77d043585 split post: SplitterControl: set a static VirtualHeight of 16. A smarter way to acheive this would be by measuring the font height 2019-06-29 18:09:56 +10:00
Nick Vella 7fc61ab713 split post: ExtendedEntrySplitterElementBehavior: only synchronize the splitter width, not height. 2019-06-29 18:09:14 +10:00
Nick Vella 150b924799 PostHtmlEditingSettings: append IE9 emulation UA compatibility, replace current UA compatibility if one already exists 2019-06-29 16:05:48 +10:00
Nick Vella 6a75beed3d default editing template: Set IE9 emulation mode to fix element behaviors 2019-06-29 16:04:54 +10:00
Jon Galloway aa5d6e2e39
Merge pull request #824 from nvella/fix-823
Partial fix for #823; stretch StatusBackground to 256 pixels
2019-06-16 16:00:51 -07:00
Jon Galloway 556195f7f6
Merge pull request #822 from nvella/fix-dpi-inconsistencies
Fix various small DPI scaling inconsistencies
2019-06-16 16:00:29 -07:00
Nick Vella 74623609f7 #823: stretch StatusBackground to 256 pixels. As the previous bg was only three pixels wide, this will reduce iterations of the tiling loop, especially on high DPI displays. 2019-06-16 23:11:08 +10:00
Nick Vella f690377f22 dpi: scale BlogPostHtmlEditorControl margins on title and content 2019-06-13 22:24:32 +10:00
Nick Vella 16a861e50a dpi: fix tab width scaling 2019-06-13 21:55:56 +10:00
Nick Vella 9683414e61 dpi: ...add more descriptive comment 2019-06-13 21:48:08 +10:00
Nick Vella 353560ddff dpi: force PostEditorFooter height to the height of it's FlowLayoutPanel. unsure as to where this extra padding is coming from 2019-06-13 21:47:50 +10:00
Nick Vella 0051af6832 dpi: add various scaling helper functions to DisplayHelper 2019-06-13 21:40:55 +10:00
Nick Vella 46846b2119 make live clipboard icon 24x24 square, fix corners 2019-05-16 20:24:01 +10:00
Nick Vella 04e9610673 dpi: fix SideBarControl button scaling 2019-05-16 18:54:34 +10:00
Nick Vella 86a9a0da3d dpi: fix SplashScreen scaling 2019-05-16 18:36:37 +10:00
Nick Vella 33cf68c1c7 dpi: scale UpdateWeblogProgressForm with ScaleBitmap 2019-05-16 18:36:27 +10:00
Nick Vella d02e120386 dpi: add ScaleBitmap static method to DisplayHelper 2019-05-16 18:36:11 +10:00
Nick Vella a1c4d542e0 dpi: remove comment 2019-05-15 23:59:03 +10:00
Nick Vella 27bc787537 dpi: scale posting animation as per DPI at startup 2019-05-15 23:55:00 +10:00
Jon Galloway b327be3858 Updated SDK version 2019-05-12 13:52:17 -07:00
Jon Galloway 54b30e330b
Merge pull request #810 from nvella/fix-786-google-photos
Replace Blogger Picasa integration with Google Drive upload (fixes #786)
2019-05-11 13:04:52 -07:00
Jon Galloway 8ec3820a5c
Fixed reference to blogger secrets
These are set in a local json file to prevent uploading secrets to GitHub, not in the writer.build.targets file.
2019-05-11 12:56:21 -07:00
Nick Vella 55b440668a blogger/gdrive: update nuspec to include Google.Apis.Drive.v3 2019-05-05 15:59:25 +10:00
Nick Vella aaf92becd6 blogger/gdrive: more detailed upload failure exception 2019-05-05 12:02:29 +10:00
Nick Vella 5df49c466d blogger/gdrive: throw more descriptive exception when file fails to upload to drive 2019-05-05 11:50:18 +10:00
Nick Vella 19a2b5c719 blogger/gdrive: remove Google.Apis.PhotosLibrary.v1 2019-05-05 00:33:22 +10:00
Nick Vella 5309ca2baf blogger/gdrive: split off download option 2019-05-05 00:13:37 +10:00
Nick Vella f5164e3464 blogger/gdrive: switch to google drive 2019-05-04 23:59:29 +10:00
Nick Vella 35e65f8a58 blogger/gdrive: remove/comment gphotos code 2019-05-04 21:11:17 +10:00
Nick Vella 8b83f29f72 blogger/gdrive: remove/comment gphotos code 2019-05-04 21:10:46 +10:00
Nick Vella 4f75609ede blogger/gphotos: remove excessive creation of library service 2019-05-04 20:47:58 +10:00
Nick Vella e1d6660119 blogger/gphotos: tidy up 2019-04-30 16:27:25 +10:00
Nick Vella fa95f4a9af blogger/gphotos: share OLW album, get download url for images, remove image editing; reupload a new image every time 2019-04-30 16:21:25 +10:00
Nick Vella 5fe336f957 blogger/gphotos: remove Picasa workaround 2019-04-30 15:17:52 +10:00
Nick Vella bf382fe513 blogger/gphotos: uploading and adding image to Open Live Writer album 2019-04-30 15:05:17 +10:00
Nick Vella 2b3d69cc3c blogger/gphotos: remove ShowPicasaSignupPrompt 2019-04-29 20:02:35 +10:00
Nick Vella 39d9ba4c03 blogger/gphotos: check for existing OLW album, create it if it doesn't exist, return the album ID 2019-04-29 19:23:57 +10:00
Nick Vella 455d385d04 Generated and included Google.Apis.PhotoLibrary.v1 from https://github.com/google/apis-client-generator and the Google PhotosLibrary API description 2019-04-29 19:12:06 +10:00
ppardi b8cb10f2bf Option to always format HTML
Added option to always format that HTML that shows up in the "source" view. This makes it easier to read and edit the HTML for the post.
2019-01-27 17:30:18 -08:00
Jon Galloway 0612102be5
Merge pull request #791 from gep13/issue-790
(GH-790) Added necessary files for embedded Chocolatey Package
2019-01-19 14:54:13 -08:00
Gary Ewan Park b6ce41f47d
(GH-790) Added necessary files for embedded Chocolatey Package 2019-01-19 22:42:21 +00:00
Jon Galloway e158d0e2bb Added deprecation header to Picasa requests 2019-01-19 14:25:57 -08:00
Jon Galloway cf8bd99017
Merge pull request #789 from Leftopia/master
Temporary fix for Picasa web requests
2019-01-19 12:46:59 -08:00
Jim Galasyn f31228ede3 Use https in picasaUri 2019-01-19 10:11:37 -08:00
Jim Galasyn f2b73351af Update per feedback: retarget solution to 10.0.17134.0 (April/1803); add the Picasa hack to BloggerAtomClient 2019-01-19 08:48:22 -08:00
Scott Lovegrove 82b0be177a [WIP] Associate .wpost files with OLW (#592)
* Add file associations

* Updated assemblyinfo

* Update package manifest
2019-01-19 02:15:25 -08:00
plieblang 6e2d8dc89a Fix crash caused by addition of Tistory support (#721) 2019-01-19 02:02:20 -08:00
Martin Brown 4fd42158b1 Upgrade to VS 2017 (#776)
* Upgrades writer solution's projects to VS2017
Re-writes build script in powershell using MS Recommended techinque for finding ms-build
Changes version number by one

* Udated BlogRunner.sln projects to Visual Studio 2017
Also fixed invalid dll references and processor architecture

* Upgraded SetupCustomCultures.sln projects to VS 2017
Upgraded the projects to use .Net Framework 4.6.1

* Updates FauxLocalizationResourceGenerator.sln projects to VS 2017
Also updates to .Net Framework 4.6.1

* Removes unwanted deploy restriction from appveyor.yml
2019-01-19 01:34:17 -08:00
Jim Galasyn f3c82fd767 Add deprecation-extension=true to Picasa web requests 2019-01-18 17:51:24 -08:00
Jim Galasyn 064d89b9f9 Retarget solution to Windows SDK version 10.0.17763.0 and Platform Toolset v141. 2019-01-18 11:31:49 -08:00
plieblang b83bb29845 Fix crash caused by addition of Tistory support 2018-02-23 17:29:25 -06:00
Josh Soref 87c151e9b1 Fix spelling errors in comments (#663)
Changes an example to use example.net and adds a trailing slash
2017-11-30 21:05:36 -08:00
manggsoft 74a39a18a1 Update master.xml (#696) 2017-11-30 15:46:23 -08:00
manggsoft cf5b42d420 Update market.xml (#695)
add tistory
2017-11-30 15:45:59 -08:00
manggsoft 7fbdd4b470 Create TistoryBlogClient.cs (#694)
Tistory oauth 2.0 of korea
2017-11-30 15:45:11 -08:00
manggsoft ed787df5bd Update BlogProvidersB5.xml (#693)
add tistory blog
2017-11-30 15:43:32 -08:00
Oren Novotny ef5856adaa use sign service v2 (#697) 2017-11-30 15:28:39 -08:00
Jon Galloway 636feec6b9
Removed vcpkg 2017-11-30 15:13:55 -08:00
Jon Galloway ffe06a3c93
Remove vcpkg from path 2017-11-30 14:35:16 -08:00
Jon Galloway 9ee603db1b
Added debugging line to test for ps path 2017-11-30 13:47:43 -08:00
Jon Galloway 02abf2269d
Update appveyor.yml 2017-11-30 13:25:25 -08:00
Jon Galloway c258b8572a
Added powershell get-date to appveyor.yml for debugging 2017-11-30 12:25:42 -08:00
411 changed files with 12998 additions and 1963 deletions

View File

@ -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="&quot;%1&quot;">
<uap:SupportedFileTypes>
<uap:FileType>.wpost</uap:FileType>
</uap:SupportedFileTypes>
<uap2:SupportedVerbs>
<uap3:Verb Id="Edit" Parameters="&quot;%1&quot;">Edit</uap3:Verb>
</uap2:SupportedVerbs>
</uap3:FileTypeAssociation>
</uap3:Extension>
</Extensions>
</Application>
</Applications>
</Package>

View File

@ -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>

View File

@ -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>

View File

@ -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

View File

@ -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"
}

View File

@ -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"
}
}
}
}

View File

@ -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:

View File

@ -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

109
build.ps1 Normal file
View File

@ -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"

2
buildstrings.cmd Normal file
View File

@ -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

23
choco/LICENSE.TXT Normal file
View File

@ -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.

10
choco/VERIFICATION.TXT Normal file
View File

@ -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

View File

@ -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.

View File

@ -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>|

View File

@ -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

View File

@ -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>

View File

@ -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>

69
logotype.svg Normal file
View File

@ -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

View File

@ -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;

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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")]

View File

@ -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>

View File

@ -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>

279
src/managed/LocEdit/MainForm.Designer.cs generated Normal file
View File

@ -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;
}
}

View File

@ -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);
}
}
}

View File

@ -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>

View File

@ -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());
}
}
}

View File

@ -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")]

View File

@ -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;
}
}
}
}

View File

@ -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>

View File

@ -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;
}
}
}
}

View File

@ -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>

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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;

View File

@ -17,7 +17,7 @@ namespace OpenLiveWriter.Api
Unknown,
/// <summary>
/// The feature is suported.
/// The feature is supported.
/// </summary>
Yes,

View File

@ -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);

View File

@ -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
{

View File

@ -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 &lt;lc:format&gt; 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

View File

@ -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>

View File

@ -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

View File

@ -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
{

View File

@ -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

View File

@ -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

View File

@ -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
{

View File

@ -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()
{

View File

@ -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()
{

View File

@ -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;

View File

@ -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;

View File

@ -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),

View File

@ -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.

View File

@ -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;

View File

@ -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;

View File

@ -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";

View File

@ -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);
}

View File

@ -32,7 +32,7 @@ namespace Project31.ApplicationFramework
}
/// <summary>
/// Menu mergers and aquisitions.
/// Menu mergers and acquisitions.
/// </summary>
internal class MergeMenu
{

View File

@ -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),

View File

@ -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)
{

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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;

View File

@ -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>

View File

@ -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.

View File

@ -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

View File

@ -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,

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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

View File

@ -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>

View File

@ -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;
}

View File

@ -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
{

View File

@ -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

View File

@ -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>"))

View File

@ -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;

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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)
{
}
}
}

View File

@ -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));
}
}
}

View File

@ -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;
}
}
}

View File

@ -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();
}
}

View File

@ -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

View File

@ -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>

View File

@ -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);

View File

@ -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('{');

View File

@ -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);

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -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>

View File

@ -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; }

View File

@ -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" />

View File

@ -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