Merge pull request #844 from nvella/static-site-generator

Initial Static Site Generator support
This commit is contained in:
Nick Vella 2019-08-18 16:11:13 +10:00 committed by GitHub
commit dbbfd4f4ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 9178 additions and 380 deletions

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

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

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

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

@ -231,7 +231,9 @@ namespace OpenLiveWriter.ApplicationFramework
button.AutoSizeWidth = false;
button.AutoSizeHeight = false;
button.Width = maxWidth;
button.Height = (int)Math.Ceiling(button.Height / (DisplayHelper.PixelsPerLogicalInchY / 96f));
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

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

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

@ -135,6 +135,7 @@ namespace OpenLiveWriter.BlogClient.Clients
AddClientType(typeof(SharePointClient));
AddClientType(typeof(WordPressClient));
AddClientType(typeof(TistoryBlogClient));
AddClientType(typeof(StaticSite.StaticSiteClient));
}
return _clientTypes;
}

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

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
@ -385,7 +383,7 @@ 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 occurred along the way, clear them since one of the template strategies
@ -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

@ -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,12 +209,12 @@ 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();
@ -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.
@ -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

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

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

@ -119,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>
@ -160,6 +163,14 @@
<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" />

View File

@ -10,5 +10,6 @@
<package id="Microsoft.Bcl.Build" version="1.0.21" targetFramework="net461" />
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net461" />
<package id="Newtonsoft.Json" version="10.0.2" targetFramework="net461" />
<package id="YamlDotNet" version="6.1.1" targetFramework="net461" />
<package id="Zlib.Portable.Signed" version="1.11.0" targetFramework="net461" />
</packages>

View File

@ -37,6 +37,16 @@ namespace OpenLiveWriter.CoreServices.Layout
groupBox.Height = ((Control)childControls[childControls.Count - 1]).Bottom + 12;
}
/// <summary>
/// Naturalizes height, then distributes vertically ACCORDING TO THE ORDER YOU PASSED THEM IN, with no DPI scaling
/// </summary>
public static void NaturalizeHeightAndDistributeNoScale(int pixelsBetween, params object[] controls)
{
NaturalizeHeight(controls);
DistributeVertically(pixelsBetween, false, false, controls);
}
/// <summary>
/// Naturalizes height, then distributes vertically ACCORDING TO THEIR Y COORDINATE
/// </summary>
@ -56,6 +66,15 @@ namespace OpenLiveWriter.CoreServices.Layout
DistributeVertically(pixelsBetween, false, controls);
}
/// <summary>
/// Naturalizes height, then distributes vertically ACCORDING TO THE ORDER YOU PASSED THEM IN
/// </summary>
public static void NaturalizeHeightAndDistribute(int pixelsBetween, bool scaleForDisplay, params object[] controls)
{
NaturalizeHeight(controls);
DistributeVertically(pixelsBetween, false, scaleForDisplay, controls);
}
/// <param name="controls">Objects of type Control or ControlGroup</param>
public static void NaturalizeHeight(params object[] controls)
{
@ -122,12 +141,56 @@ namespace OpenLiveWriter.CoreServices.Layout
}
}
/// <summary>
/// Space out a set of controls down the Y axis with no DPI scaling
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeVerticallyNoScale(int pixelsBetween, params object[] controls)
{
DistributeVertically(pixelsBetween, false, false, controls);
}
/// <summary>
/// Space out a set of controls down the Y axis with no DPI scaling
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="sortByVerticalPosition">If true, controls are sorted by their vertical position before distribution</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeVerticallyNoScale(int pixelsBetween, bool sortByVerticalPosition, params object[] controls)
{
DistributeVertically(pixelsBetween, sortByVerticalPosition, false, controls);
}
/// <summary>
/// Space out a set of controls down the Y axis
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeVertically(int pixelsBetween, params object[] controls)
{
DistributeVertically(pixelsBetween, false, controls);
}
/// <summary>
/// Space out a set of controls down the Y axis
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="sortByVerticalPosition">If true, controls are sorted by their vertical position before distribution</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeVertically(int pixelsBetween, bool sortByVerticalPosition, params object[] controls)
{
DistributeVertically(pixelsBetween, sortByVerticalPosition, true, controls);
}
/// <summary>
/// Space out a set of controls down the Y axis
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="sortByVerticalPosition">If true, controls are sorted by their vertical position before distribution</param>
/// <param name="scaleForDisplay">If true, spacing is scaled to match display scaling factor</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeVertically(int pixelsBetween, bool sortByVerticalPosition, bool scaleForDisplay, params object[] controls)
{
if (controls.Length < 2)
return;
@ -140,7 +203,8 @@ namespace OpenLiveWriter.CoreServices.Layout
#endif
*/
pixelsBetween = Ceil(DisplayHelper.ScaleY(pixelsBetween));
if(scaleForDisplay) pixelsBetween = Ceil(DisplayHelper.ScaleY(pixelsBetween));
if (sortByVerticalPosition)
Array.Sort(controls, new SortByVerticalPosition());
int pos = ControlAdapter.Create(controls[0]).Bottom + pixelsBetween;
@ -155,7 +219,33 @@ namespace OpenLiveWriter.CoreServices.Layout
}
}
/// <summary>
/// Space out a set of controls down the X axis with no DPI scaling
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeHorizontallyNoScale(int pixelsBetween, params object[] controls)
{
DistributeHorizontally(pixelsBetween, false, controls);
}
/// <summary>
/// Space out a set of controls down the X axis
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeHorizontally(int pixelsBetween, params object[] controls)
{
DistributeHorizontally(pixelsBetween, true, controls);
}
/// <summary>
/// Space out a set of controls down the X axis
/// </summary>
/// <param name="pixelsBetween">Amount of pixels between each control</param>
/// <param name="scaleForDisplay">If true, spacing is scaled to match display scaling factor</param>
/// <param name="controls">The controls to distribute</param>
public static void DistributeHorizontally(int pixelsBetween, bool scaleForDisplay, params object[] controls)
{
if (controls.Length < 2)
return;
@ -168,7 +258,7 @@ namespace OpenLiveWriter.CoreServices.Layout
#endif
*/
pixelsBetween = Ceil(DisplayHelper.ScaleX(pixelsBetween));
if(scaleForDisplay) pixelsBetween = Ceil(DisplayHelper.ScaleX(pixelsBetween));
Array.Sort(controls, new SortByHorizontalPosition());
int pos = ControlAdapter.Create(controls[0]).Right + pixelsBetween;
for (int i = 1; i < controls.Length; i++)

View File

@ -297,5 +297,13 @@ namespace OpenLiveWriter.CoreServices
}
return false;
}
/// <summary>
/// Removes leading and trailing slashes on a path, if they exist.
/// </summary>
/// <param name="path">The path to process</param>
/// <returns></returns>
public static string RemoveLeadingAndTrailingSlash(string path)
=> path.Trim(new char[] {'/', '\\'});
}
}

View File

@ -120,7 +120,6 @@ namespace OpenLiveWriter.CoreServices
try
{
HttpWebResponse response = (HttpWebResponse)request.GetResponse();
//hack: For some reason, disabling auto-redirects also disables throwing WebExceptions for 300 status codes,
//so if we detect a non-2xx error code here, throw a web exception.
int statusCode = (int)response.StatusCode;
@ -281,7 +280,8 @@ namespace OpenLiveWriter.CoreServices
//Warning: NTLM authentication requires keep-alive, so without adjusting this, NTLM-secured requests will always fail.
request.KeepAlive = false;
request.Pipelined = false;
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Reload);
// Bypass cache entirely - some blogs, specifically static blogs on GH pages, have very aggressive caching policies
request.CachePolicy = new RequestCachePolicy(RequestCacheLevel.BypassCache);
return request;
}

View File

@ -107,6 +107,12 @@ namespace OpenLiveWriter.Extensibility.BlogClient
/// Returns false if credentials are sent in the clear
/// </summary>
bool IsSecure { get; }
/// <summary>
/// Returns false if it is not possible to download manifests or templates for detection
/// eg. for local static sites
/// </summary>
bool RemoteDetectionPossible { get; }
}
public interface INewCategoryContext

View File

@ -1,5 +1,9 @@
// 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
{
@ -15,7 +19,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
AboutConfigurationVersion,
/// <summary>
/// © 2015 .NET Foundation. All rights reserved.
/// Copyright © .NET Foundation. All rights reserved.
/// </summary>
AboutCopyright,
/// <summary>
@ -843,7 +847,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
Comma,
/// <summary>
/// ,
/// ,
/// </summary>
CommaSpace,
/// <summary>
@ -1035,6 +1039,18 @@ namespace OpenLiveWriter.Localization
/// </summary>
CWConfirmWeblogName,
/// <summary>
/// To configure Google Blogger please sign in.
/// </summary>
CWGoogleBloggerDescription,
/// <summary>
/// Successfully signed in
/// </summary>
CWGoogleBloggerSignInSuccess,
/// <summary>
/// Provide Google Blogger Login
/// </summary>
CWGoogleBloggerTitle,
/// <summary>
/// If you use Hotmail, Messenger, or Xbox LIVE, you have a Microsoft Account.
/// </summary>
CWLiveIDCreateAccount,
@ -1103,18 +1119,6 @@ namespace OpenLiveWriter.Localization
/// </summary>
CWSharePointUseSystemLogin,
/// <summary>
/// Provide Google Blogger Login
/// </summary>
CWGoogleBloggerTitle,
/// <summary>
/// To configure Google Blogger please sign in.
/// </summary>
CWGoogleBloggerDescription,
/// <summary>
/// Successfully signed in
/// </summary>
CWGoogleBloggerSignInSuccess,
/// <summary>
/// &Microsoft Account:
/// </summary>
CWSpacesUsername,
@ -1123,6 +1127,110 @@ namespace OpenLiveWriter.Localization
/// </summary>
CWSpacesUsernameExample,
/// <summary>
/// Build command:
/// </summary>
CWStaticSiteCommandsBuildCommand,
/// <summary>
/// Command that builds your site and stores the output locally, ready to be published. Required if Local Site Building enabled.
/// </summary>
CWStaticSiteCommandsBuildCommandSubtitle,
/// <summary>
/// Publish command:
/// </summary>
CWStaticSiteCommandsPublishCommand,
/// <summary>
/// Command that uploads your site to your hosting provider. If Local Site Building is enabled, this command would typically upload the output from the build command.
/// </summary>
CWStaticSiteCommandsPublishCommandSubtitle,
/// <summary>
/// {0} will run the commands you specify below and alert you if an error occurs. Commands are ran using the system command interpreter with a working directory of your local site.
/// </summary>
CWStaticSiteCommandsSubtitle,
/// <summary>
/// Provide site authoring commands
/// </summary>
CWStaticSiteCommandsTitle,
/// <summary>
/// {0} was able to automatically determine a partial configuration for your site. Please confirm that the pre-filled details on the next pages are correct, and complete the other missing fields.
/// </summary>
CWStaticSiteConfigDetection,
/// <summary>
/// Enable Local Site Building
/// </summary>
CWStaticSiteFeaturesBuilding,
/// <summary>
/// Enable Remote Drafts
/// </summary>
CWStaticSiteFeaturesDrafts,
/// <summary>
/// Enable Image Upload
/// </summary>
CWStaticSiteFeaturesImages,
/// <summary>
/// Enable Pages
/// </summary>
CWStaticSiteFeaturesPages,
/// <summary>
/// Use the checkboxes below to define which features your static site supports. You can configure Open Live Writer for these features on the following pages.
/// </summary>
CWStaticSiteFeaturesSubtitle,
/// <summary>
/// Define static site features
/// </summary>
CWStaticSiteFeaturesTitle,
/// <summary>
/// {0} will attempt to automatically detect your static site configuration based on files present in your site project folder. Please select the project folder of your static site (eg. Git repository)
/// </summary>
CWStaticSiteInitialSubtitle,
/// <summary>
/// {0} has already attempted configuration detection on your site. You can change the path to your site here if you wish. Otherwise, you can re-run configuration detection from Settings.
/// </summary>
CWStaticSiteInitialSubtitleAlreadyDetected,
/// <summary>
/// Provide static site configuration
/// </summary>
CWStaticSiteInitialTitle,
/// <summary>
/// Please select the project folder of your static site (eg. Git repository)
/// </summary>
CWStaticSiteLocalSiteFolderPicker,
/// <summary>
/// Path to local static site:
/// </summary>
CWStaticSiteLocalSitePath,
/// <summary>
/// Drafts path: (relative, required if drafts enabled)
/// </summary>
CWStaticSitePathsDraftsPath,
/// <summary>
/// Images path: (relative, required if images enabled)
/// </summary>
CWStaticSitePathsImagesPath,
/// <summary>
/// Build output path: (relative, required if building enabled)
/// </summary>
CWStaticSitePathsOutputPath,
/// <summary>
/// Pages stored in site root
/// </summary>
CWStaticSitePathsPagesInRoot,
/// <summary>
/// Pages path: (relative, required if pages enabled)
/// </summary>
CWStaticSitePathsPagesPath,
/// <summary>
/// Posts path: (relative)
/// </summary>
CWStaticSitePathsPostsPath,
/// <summary>
/// Public site URL:
/// </summary>
CWStaticSitePathsSiteUrl,
/// <summary>
/// Provide site URL and paths
/// </summary>
CWStaticSitePathsTitle,
/// <summary>
/// Add Blog Wizard
/// </summary>
CWTitle,
@ -2051,7 +2159,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
HelpButton,
/// <summary>
/// Hide Properties
/// Hide Properties
/// </summary>
HideProperties,
/// <summary>
@ -3124,10 +3232,14 @@ namespace OpenLiveWriter.Localization
/// </summary>
PasteSpecialThinnedLabel,
/// <summary>
/// Percent (unit of measure)
/// percent
/// </summary>
Percent,
/// <summary>
/// Inline Photo Previewer
/// </summary>
PhotoPreview,
/// <summary>
/// Ping Servers
/// </summary>
PingPrefName,
@ -3136,7 +3248,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
PingPrefUrl,
/// <summary>
/// Pixels (unit of measure)
/// pixels
/// </summary>
Pixels,
/// <summary>
@ -3572,7 +3684,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
Plugin_Video_Youtube_Publish_Name,
/// <summary>
/// Yes, I agree to the YouTube Terms of Use and that I own all copyrights in this video or have authorization to upload it. Do not upload any TV shows, music videos, music concerts, or commercials without permission unless they consist entirely of content you created yourself.
/// Yes, I agree to the YouTube Terms of Use and that I own all copyrights in this video or have authorization to upload it. Do not upload any TV shows, music videos, music concerts, or commercials without permission unless they consist entirely of content you created yourself.
/// </summary>
Plugin_Video_YouTube_Publish_Terms_Agree,
/// <summary>
@ -3704,7 +3816,11 @@ namespace OpenLiveWriter.Localization
/// </summary>
PostEditorPrefAuto,
/// <summary>
/// Close &window after publishing:
/// Browse
/// </summary>
PostEditorPrefBrowseFolder,
/// <summary>
/// Close &window after publishing
/// </summary>
PostEditorPrefClose,
/// <summary>
@ -3716,10 +3832,6 @@ namespace OpenLiveWriter.Localization
/// </summary>
PostEditorPrefGeneral,
/// <summary>
/// Folder Location for Posts
/// </summary>
PostEditorPrefPostLocation,
/// <summary>
/// Preferences
/// </summary>
PostEditorPrefName,
@ -3728,6 +3840,10 @@ namespace OpenLiveWriter.Localization
/// </summary>
PostEditorPrefNew,
/// <summary>
/// Local drafts and recent posts folder
/// </summary>
PostEditorPrefPostLocation,
/// <summary>
/// Post window
/// </summary>
PostEditorPrefPostWindows,
@ -3760,14 +3876,10 @@ namespace OpenLiveWriter.Localization
/// </summary>
PostEditorPrefUnsave,
/// <summary>
/// &View blog after publishing - Modified comment by @kathweaver for issue #377
/// &View blog after publishing
/// </summary>
PostEditorPrefView,
/// <summary>
/// Browse for a folder
/// </summary>
PostEditorPrefBrowseFolder,
/// <summary>
/// Unexpected error occurred while accessing local post ({0})
///
/// {1}
@ -3958,6 +4070,10 @@ namespace OpenLiveWriter.Localization
/// </summary>
ProgressDownloadingWeblogEditingStyle,
/// <summary>
/// Post contents not present on homepage, checking post...
/// </summary>
ProgressDownloadingWeblogEditingStyleDeep,
/// <summary>
/// Finalizing editing template configuration...
/// </summary>
ProgressFinalizingEditingTemplateConfig,
@ -4372,7 +4488,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
SpellText,
/// <summary>
/// px
/// px
/// </summary>
SpinnerPixelFormatString,
/// <summary>
@ -4380,7 +4496,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
SpinnerPixelRepresentativeString,
/// <summary>
/// © 2015 .NET Foundation. All rights reserved.
/// Copyright © .NET Foundation. All rights reserved.
/// </summary>
SplashScreenCopyrightNotice,
/// <summary>
@ -4388,6 +4504,320 @@ namespace OpenLiveWriter.Localization
/// </summary>
SplitterMore,
/// <summary>
/// {0} has failed to build your site. Please ensure your site builds manually, check your build command and try again.
///
/// Build command exit code: {1}
/// Command STDOUT:
/// {2}
/// Command STDERR:
/// {3}
///
/// </summary>
SSGBuildErrorText,
/// <summary>
/// Static site build failed
/// </summary>
SSGBuildErrorTitle,
/// <summary>
/// Drafts Path: (relative)
/// </summary>
SSGConfigAuthoringDraftsPath,
/// <summary>
/// Enable &Drafts
/// </summary>
SSGConfigAuthoringEnableDrafts,
/// <summary>
/// Enable &Images
/// </summary>
SSGConfigAuthoringEnableImages,
/// <summary>
/// Enable P&ages
/// </summary>
SSGConfigAuthoringEnablePages,
/// <summary>
/// Images
/// </summary>
SSGConfigAuthoringImagesGroup,
/// <summary>
/// Images Path: (relative)
/// </summary>
SSGConfigAuthoringImagesPath,
/// <summary>
/// Pages
/// </summary>
SSGConfigAuthoringPagesGroup,
/// <summary>
/// Pages Stored In Project Root
/// </summary>
SSGConfigAuthoringPagesInRoot,
/// <summary>
/// Pages Path: (relative)
/// </summary>
SSGConfigAuthoringPagesPath,
/// <summary>
/// Posts and Drafts
/// </summary>
SSGConfigAuthoringPostsDraftsGroup,
/// <summary>
/// &Posts Path: (relative)
/// </summary>
SSGConfigAuthoringPostsPath,
/// <summary>
/// Authoring
/// </summary>
SSGConfigAuthoringTitle,
/// <summary>
/// Build Command:
/// </summary>
SSGConfigBuildPublishBuildCommand,
/// <summary>
/// Building
/// </summary>
SSGConfigBuildPublishBuildingGroup,
/// <summary>
/// &Command Timeout (ms):
/// </summary>
SSGConfigBuildPublishCmdTimeout,
/// <summary>
/// Enable &Building
/// </summary>
SSGConfigBuildPublishEnableBuilding,
/// <summary>
/// &Enable Command Timeout
/// </summary>
SSGConfigBuildPublishEnableCmdTimeout,
/// <summary>
/// General
/// </summary>
SSGConfigBuildPublishGeneralGroup,
/// <summary>
/// Site Output Path: (relative)
/// </summary>
SSGConfigBuildPublishOutputPath,
/// <summary>
/// &Publish Command:
/// </summary>
SSGConfigBuildPublishPublishCommand,
/// <summary>
/// Publishing
/// </summary>
SSGConfigBuildPublishPublishingGroup,
/// <summary>
/// &Show Command Windows
/// </summary>
SSGConfigBuildPublishShowCmdWindows,
/// <summary>
/// Building and Publishing
/// </summary>
SSGConfigBuildPublishTitle,
/// <summary>
/// Front Matter Key
/// </summary>
SSGConfigFrontMatterKeyCol,
/// <summary>
/// Property
/// </summary>
SSGConfigFrontMatterPropertyCol,
/// <summary>
/// &Reset to Defaults
/// </summary>
SSGConfigFrontMatterReset,
/// <summary>
/// Below you can adjust the post front matter keys used to match your static site generator.
/// </summary>
SSGConfigFrontMatterSubtitle,
/// <summary>
/// Front Matter
/// </summary>
SSGConfigFrontMatterTitle,
/// <summary>
/// Run Auto-&Detect
/// </summary>
SSGConfigGeneralDetectButton,
/// <summary>
/// Open Live Writer can also reattempt to detect relevant configuration options for your static site. This may not result in a complete configuration, so please use the fields on the following pages to ensure all settings are set correctly.
/// </summary>
SSGConfigGeneralDetectLabel,
/// <summary>
/// &Local Site Path:
/// </summary>
SSGConfigGeneralLocalSitePath,
/// <summary>
/// Options
/// </summary>
SSGConfigGeneralOptionsGroup,
/// <summary>
/// Setup
/// </summary>
SSGConfigGeneralSetupGroup,
/// <summary>
/// Site &Title:
/// </summary>
SSGConfigGeneralSiteTitle,
/// <summary>
/// Site &URL:
/// </summary>
SSGConfigGeneralSiteUrl,
/// <summary>
/// General
/// </summary>
SSGConfigGeneralTitle,
/// <summary>
/// Run Account &Wizard
/// </summary>
SSGConfigGeneralWizardButton,
/// <summary>
/// You can choose to run the Account Wizard again if you wish to be guided through the core static site configuration options interactively.
/// </summary>
SSGConfigGeneralWizardLabel,
/// <summary>
/// Static Site Configuration for '{0}'
/// </summary>
SSGConfigTitle,
/// <summary>
/// A build command is required when local site building is enabled.
/// </summary>
SSGErrorBuildCommandEmptyText,
/// <summary>
/// Build command empty
/// </summary>
SSGErrorBuildCommandEmptyTitle,
/// <summary>
/// Blog command timed out. Please check your commands, or lengthen the command timeout.
/// </summary>
SSGErrorCommandTimeoutText,
/// <summary>
/// Command execution timeout
/// </summary>
SSGErrorCommandTimeoutTitle,
/// <summary>
/// Could not read item front matter.
/// </summary>
SSGErrorItemLoadTextFM,
/// <summary>
/// Item does not have an ID.
/// </summary>
SSGErrorItemLoadTextId,
/// <summary>
/// Item load error
/// </summary>
SSGErrorItemLoadTitle,
/// <summary>
/// Could not find page with specified ID.
/// </summary>
SSGErrorPageDoesNotExistText,
/// <summary>
/// Page does not exist
/// </summary>
SSGErrorPageDoesNotExistTitle,
/// <summary>
/// Drafts path is empty.
/// </summary>
SSGErrorPathDraftsEmpty,
/// <summary>
/// Drafts path '{0}' does not exist.
/// </summary>
SSGErrorPathDraftsNotFound,
/// <summary>
/// Folder not found
/// </summary>
SSGErrorPathFolderNotFound,
/// <summary>
/// Images path is empty.
/// </summary>
SSGErrorPathImagesEmpty,
/// <summary>
/// Images path '{0}' does not exist.
/// </summary>
SSGErrorPathImagesNotFound,
/// <summary>
/// Local site path '{0}' does not exist.
/// </summary>
SSGErrorPathLocalSitePathNotFound,
/// <summary>
/// Output path is empty.
/// </summary>
SSGErrorPathOutputEmpty,
/// <summary>
/// Output path '{0}' does not exist.
/// </summary>
SSGErrorPathOutputNotFound,
/// <summary>
/// Pages path is empty.
/// </summary>
SSGErrorPathPagesEmpty,
/// <summary>
/// Pages path '{0}' does not exist.
/// </summary>
SSGErrorPathPagesNotFound,
/// <summary>
/// Posts path is empty.
/// </summary>
SSGErrorPathPostsEmpty,
/// <summary>
/// Posts path '{0}' does not exist.
/// </summary>
SSGErrorPathPostsNotFound,
/// <summary>
/// Could not find post with specified ID.
/// </summary>
SSGErrorPostDoesNotExistText,
/// <summary>
/// Post does not exist
/// </summary>
SSGErrorPostDoesNotExistTitle,
/// <summary>
/// A publish command is required.
/// </summary>
SSGErrorPublishCommandEmptyText,
/// <summary>
/// Publish command empty
/// </summary>
SSGErrorPublishCommandEmptyTitle,
/// <summary>
/// Date
/// </summary>
SSGFrontMatterDate,
/// <summary>
/// ID
/// </summary>
SSGFrontMatterId,
/// <summary>
/// Layout
/// </summary>
SSGFrontMatterLayout,
/// <summary>
/// Parent ID
/// </summary>
SSGFrontMatterParentId,
/// <summary>
/// Permalink
/// </summary>
SSGFrontMatterPermalink,
/// <summary>
/// Tags
/// </summary>
SSGFrontMatterTags,
/// <summary>
/// Title
/// </summary>
SSGFrontMatterTitle,
/// <summary>
/// {0} has failed to publish your site. Please check your site publish command.
///
/// Publish command exit code: {1}
/// Command STDOUT:
/// {2}
/// Command STDERR:
/// {3}
///
/// </summary>
SSGPublishErrorText,
/// <summary>
/// Static site publish failed
/// </summary>
SSGPublishErrorTitle,
/// <summary>
/// Statistics
/// </summary>
Statistics,
@ -4652,7 +5082,7 @@ namespace OpenLiveWriter.Localization
/// </summary>
ToolbarBoldLetter,
/// <summary>
///
///
/// </summary>
ToolbarFontStyleFontFamily,
/// <summary>
@ -4688,10 +5118,6 @@ namespace OpenLiveWriter.Localization
/// </summary>
UnexpectedErrorExit,
/// <summary>
/// Send Error
/// </summary>
UnexpectedErrorSendError,
/// <summary>
/// An error occurred in the {0} plug-in:
///
/// {1}
@ -4702,6 +5128,10 @@ namespace OpenLiveWriter.Localization
/// </summary>
UnexpectedErrorPluginTitle,
/// <summary>
/// &Send Error
/// </summary>
UnexpectedErrorSendError,
/// <summary>
/// Unexpected Error
/// </summary>
UnexpectedErrorTitle,
@ -5047,10 +5477,6 @@ namespace OpenLiveWriter.Localization
/// </summary>
WidthLabel,
/// <summary>
/// Inline Photo Previewer
/// </summary>
PhotoPreview,
/// <summary>
/// {0} - {1}
/// </summary>
WindowTitleFormat,
@ -5079,7 +5505,11 @@ namespace OpenLiveWriter.Localization
/// </summary>
WizardBlogTypeSharePoint,
/// <summary>
/// Many popular blog services work with {0}. If you don't have a blog, click Create a new blog.
/// Static Site G&enerator
/// </summary>
WizardBlogTypeStaticSite,
/// <summary>
/// Many popular blog services work with {0}.
/// </summary>
WizardBlogTypeWelcome,
/// <summary>
@ -5176,4 +5606,3 @@ namespace OpenLiveWriter.Localization
YouTubeVideoError
}
}

View File

@ -49,17 +49,18 @@ MapPushpinTip,Tip: Right-click the map to add a pin,
MapRoad,Road,
MapAerial,Aerial,
MapBirdseye,Bird's eye,
SemanticHtmlPreviewText,AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz, Sample text rendered to give the user an idea what each style in the HTML styles gallery looks like,
SemanticHtmlPreviewText,AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz,Sample text rendered to give the user an idea what each style in the HTML styles gallery looks like
AddButton,A&dd...,"Add new blog, add to link glossary, etc."
EditButton,&Edit...,"Edit blog, glossary entry, etc."
DeleteButton,Re&move...,"Delete blog, glossary entry, etc."
ViewButton,&View,View blog
ConfigureProduct,Configure {0},"{0} - Product name, i.e. ""Open Live Writer"""
WizardBlogTypeWelcome,"Many popular blog services work with {0}.","{0} - Long product name, i.e. ""Open Live Writer"""
WizardBlogTypeWelcome,Many popular blog services work with {0}.,"{0} - Long product name, i.e. ""Open Live Writer"""
WizardBlogTypeWhatBlogType,What blog service do you use?,
WizardBlogTypeConfigureTo,Configure {0} to publish to:,"{0} - Long product name, i.e. ""Open Live Writer""; followed by a choice of two options: ""SharePoint blog"" or ""Another blog service"""
WizardBlogTypeSharePoint,&SharePoint,
WizardBlogTypeGoogleBlogger,&Google Blogger,
WizardBlogTypeStaticSite,Static Site G&enerator,
WizardBlogTypeOther,&Other services,
Options,Options,
AllowAutoUpdate,"Automatically &update account information (categories, links, capabilities, and provider extensions)",
@ -71,7 +72,7 @@ PostEditorPrefPublishing,Publishing,
PostEditorPrefRemind,Remind me to add &tags before publishing,
PostEditorPrefRemindCat,Remind me to add &categories before publishing,
PostEditorPrefClose,Close &window after publishing,
PostEditorPrefView,&View blog after publishing,
PostEditorPrefView,&View blog after publishing,Modified on 2/19/2016 by @kathweaver to resolve Issue #377
PostEditorPrefAuto,Save A&utoRecover information periodically,
PostEditorPrefSide,S&how taskpane when editable items are inserted,
PostEditorPrefUnsave,Open a new window &only when there are unsaved changes to the current post,
@ -80,10 +81,10 @@ PostEditorPrefNew,Open a new window for &each post,
PostEditorPrefTitle,&Remind me to type a title before publishing,
PostEditorPrefName,Preferences,
PostEditorPrefGeneral,General options,
PostEditorPrefPostLocation,Local drafts and recent posts folder,
PostEditorPrefBrowseFolder,Browse,
SpellingPrefOptions,General options,
SpellingPrefReal,Use &real-time spell checking (squiggles),
SpellingPrefNum,Ignore words with &numbers,
SpellingPrefUpper,Ignore words in &UPPERCASE,
SpellingPrefAuto,Automatically &correct common capitalization and spelling mistakes,
SpellingPrefGroup,Spelling form options,
SpellingPrefPub,Check spelling before &publishing,
@ -127,20 +128,20 @@ FtpUsername,&User name:,
FtpPassword,&Password:,
ImagesPanel,Pictures,
EditingStyle,Blog theme,
EditingText,After you make changes to your theme online, click Update theme to see the latest.,
EditingText,After you make changes to your theme online,click Update theme to see the latest.
EditingUsing,Use your blog theme to see what your post will look like online while you are editing it.,{0} - Open Live Writer
EditingUpdate,&Update theme,
EditingName,Editing,
EditingRTLName,Text direction,
EditingRTLYes,Right to Left,
EditingRTLNo,Left to Right,
EditingRTLDefault,Default ({0}), {0} - EditingRTLYes or EditingRTLNo
EditingRTLDefault,Default ({0}),{0} - EditingRTLYes or EditingRTLNo
EditingRTLExplanation,Change the reading direction of your blog posts.,Explanation for the right-to-left setting
EditingRTLUse,&Enter text Right to Left in Edit view.
EditingRTLUse,&Enter text Right to Left in Edit view.,
AdvancedTransport,Character set,
AdvancedText,"Select another encoding type for your blog posts. By default, it's UTF-8.",
AdvancedName,Advanced,
AdvancedDefault,Default ({0}),"{0} - default encoding web name (e.g. ""UTF-8"")"
AdvancedDefault,Default ({0}),{0} - default encoding web name (e.g. "UTF-8")
AdvancedXHTMLGroupName,Markup language,
AdvancedXHTMLLabel,"Select HTML or XHTML for your markup language type. By default, it's HTML.",
AdvancedXHTMLDefault,Default ({0}),{0} - yes or no
@ -151,7 +152,7 @@ FtpHostname,&Host name:,
FtpText,FTP Settings,
FtpLoginDomain,FTP,
SpellNotInDict,&Not in dictionary:,
SpellAdd,A&dd,"Add misspelled word to dictionary"
SpellAdd,A&dd,Add misspelled word to dictionary
SpellChange,C&hange to:,
SpellOptions,S&uggestions:,
SpellIgnore,I&gnore,
@ -181,10 +182,10 @@ InsertPictureTooltip,Insert a picture into the current document,
InsertTableDotDotDot,Table...,
InsertTable,Insert Table,
InsertTableTooltip,Insert a table into the current document,
EmoticonsGalleryRecent,Recent,This is a category header that appears above the last 10 recently used emoticons,
EmoticonsGalleryPopular,All,This is a category header that appears above all the available emoticons,
EmoticonsTooltipOneAutoReplace,{0} {1}, "{0} - description of emoticon, {1} - text form of emoticon e.g. :-)"
EmoticonsTooltipTwoAutoReplace,{0} {1} or {2}, "{0} - description of emoticon, {1} - 1st text form of emoticon e.g. :-), {2} - 2nd text form of emoticon e.g. :)"
EmoticonsGalleryRecent,Recent,This is a category header that appears above the last 10 recently used emoticons
EmoticonsGalleryPopular,All,This is a category header that appears above all the available emoticons
EmoticonsTooltipOneAutoReplace,{0} {1},"{0} - description of emoticon, {1} - text form of emoticon e.g. :-)"
EmoticonsTooltipTwoAutoReplace,{0} {1} or {2},"{0} - description of emoticon, {1} - 1st text form of emoticon e.g. :-), {2} - 2nd text form of emoticon e.g. :)"
EmoticonSmile,Smile,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonSurprisedSmile,Surprised smile,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonWinkingSmile,Winking smile,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
@ -267,7 +268,6 @@ EmoticonDogFace,Dog face,Alternate text for the emoticon. The emoticon is from M
EmoticonLightBulb,Light bulb,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonSleepingHalfMoon,Sleeping half-moon,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonEmail,Email,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonMessenger,Messenger,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonBlackSheep,Black Sheep,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonBowl,Bowl,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonSoccerBall,Soccer ball,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
@ -288,7 +288,6 @@ EmoticonBunny,Bunny,Alternate text for the emoticon. The emoticon is from Messen
EmoticonSchoolBus,School bus,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonPeace,Peace,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonSchool,School,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
EmoticonXbox,Xbox,Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.
PublishDatePrompt,Set post date,No apostrophes (') allowed in this message
PublishDateTooltip,Post date,
PropertiesPanel,Post Properties,This is an accessibility name and should be localized.
@ -321,7 +320,7 @@ CategoryControlCategories,Categories: {0},{0} - comma-delimited list of categori
CategoryControlCategory,Category: {0},{0} - the single category that is selected
CategoryControlNoCategories,(none),
CategoryControlNoCategories2,(No categories),
DynamicCommandMenuMore,Mo&re...,If all the options can't be shown in one menu then this is the last option in a menu and it launches a form that shows all the options,
DynamicCommandMenuMore,Mo&re...,If all the options can't be shown in one menu then this is the last option in a menu and it launches a form that shows all the options
DynamicCommandMenuInsert,Insert,
TagControlSetTags,Set tags (comma separated),
AlignMenuItem,&Align,From the context menu when right-clicking on text in the editor.
@ -355,11 +354,11 @@ CWSelectBlogText,More than one blog was detected. Please select the blog that yo
CWSelectImageEndpointText,Please select the collection that you'd like images to be uploaded to:,
PasswordChar,0x25CF,The hex value of the single character that will be used to represent each character in a password field.
ViewWeblog,View Blog,
CWSpacesUsername,&Microsoft Account ID:,
CWLiveIDCreateAccount,"If you use Hotmail, Messenger, or Xbox LIVE, you have a Microsoft Account ID.",
CWSpacesUsername,&Microsoft Account:,
CWLiveIDCreateAccount,"If you use Hotmail, Messenger, or Xbox LIVE, you have a Microsoft Account.",
RememberPassword,&Remember my password,
CWSpacesUsernameExample,(example555@hotmail.com),Example of Microsoft Account ID
CWLiveIDCreateAccount2,Don't have a Microsoft Account ID?,
CWSpacesUsernameExample,(example555@hotmail.com),Example of Microsoft Account
CWLiveIDCreateAccount2,Don't have a Microsoft Account?,
CWSharePointText,To use {0} you must have a SharePoint blog.,{0} - Open Live Writer
CWSharePointHomepageUrl,Share&Point blog web address:,
CWSharePointTitle,Configure {0} for a SharePoint blog,{0} - Writer
@ -369,6 +368,50 @@ CWSharePointUseSystemLogin,Use my &Windows user name and password,
CWGoogleBloggerTitle,Provide Google Blogger Login,
CWGoogleBloggerDescription,To configure Google Blogger please sign in.,
CWGoogleBloggerSignInSuccess,Successfully signed in,
CWStaticSiteInitialTitle,Provide static site configuration,
CWStaticSiteInitialSubtitle,{0} will attempt to automatically detect your static site configuration based on files present in your site project folder. Please select the project folder of your static site (eg. Git repository),{0} - Product name
CWStaticSiteInitialSubtitleAlreadyDetected,"{0} has already attempted configuration detection on your site. You can change the path to your site here if you wish. Otherwise, you can re-run configuration detection from Settings.",{0} - Product name
CWStaticSiteFeaturesTitle,Define static site features,
CWStaticSiteFeaturesSubtitle,Use the checkboxes below to define which features your static site supports. You can configure Open Live Writer for these features on the following pages.,
CWStaticSiteFeaturesPages,Enable Pages,
CWStaticSiteFeaturesDrafts,Enable Remote Drafts,
CWStaticSiteFeaturesImages,Enable Image Upload,
CWStaticSiteFeaturesBuilding,Enable Local Site Building,
CWStaticSitePathsTitle,Provide site URL and paths,
CWStaticSitePathsSiteUrl,Public site URL:,
CWStaticSitePathsPostsPath,Posts path: (relative),
CWStaticSitePathsPagesPath,"Pages path: (relative, required if pages enabled)",
CWStaticSitePathsPagesInRoot,Pages stored in site root,
CWStaticSitePathsDraftsPath,"Drafts path: (relative, required if drafts enabled)",
CWStaticSitePathsImagesPath,"Images path: (relative, required if images enabled)",
CWStaticSitePathsOutputPath,"Build output path: (relative, required if building enabled)",
CWStaticSiteCommandsTitle,Provide site authoring commands,
CWStaticSiteCommandsSubtitle,{0} will run the commands you specify below and alert you if an error occurs. Commands are ran using the system command interpreter with a working directory of your local site.,{0} - Product name
CWStaticSiteCommandsBuildCommand,Build command:,
CWStaticSiteCommandsBuildCommandSubtitle,"Command that builds your site and stores the output locally, ready to be published. Required if Local Site Building enabled.",
CWStaticSiteCommandsPublishCommand,Publish command:,
CWStaticSiteCommandsPublishCommandSubtitle,"Command that uploads your site to your hosting provider. If Local Site Building is enabled, this command would typically upload the output from the build command.",
CWStaticSiteConfigDetection,"{0} was able to automatically determine a partial configuration for your site. Please confirm that the pre-filled details on the next pages are correct, and complete the other missing fields.",{0} - Product name
CWStaticSiteLocalSitePath,Path to local static site:,
CWStaticSiteLocalSiteFolderPicker,Please select the project folder of your static site (eg. Git repository),
SSGBuildErrorTitle,Static site build failed,
SSGBuildErrorText,"{0} has failed to build your site. Please ensure your site builds manually, check your build command and try again.
Build command exit code: {1}
Command STDOUT:
{2}
Command STDERR:
{3}
","{0} - Product name, {1} - Build command exit code"
SSGPublishErrorTitle,Static site publish failed,
SSGPublishErrorText,"{0} has failed to publish your site. Please check your site publish command.
Publish command exit code: {1}
Command STDOUT:
{2}
Command STDERR:
{3}
","{0} - Product name, {1} - Publish command exit code"
CWTitle,Add Blog Wizard,Caption of the Add Blog Wizard window.
CWProgressHeader,Setting up your blog account,
ProgressDownloadingEditingTemplate,Downloading editing template...,
@ -379,6 +422,7 @@ ProgressDetectingWeblogCategories,Detecting blog categories...,
ProgressDetectingWeblogIcon,Detecting blog icon...,
ProgressCompletedSettingsDetection,Completed settings detection,
ProgressDownloadingWeblogEditingStyle,Downloading blog editing theme...,
ProgressDownloadingWeblogEditingStyleDeep,"Post contents not present on homepage, checking post...",Used when downloading template from a post page is required
ProgressSaving,Saving {0}...,{0} - a filename
ProgressDownloadFinished,Download finished,
ProgressDownloading,Downloading {0}...,{0} - a URL
@ -399,7 +443,7 @@ CWConfirmWeblogName,Blog Name,
CWConfirmSwitchToWeblog,&Switch to this blog now,
CWConfirmFTPServer,FTP Server,
CWConfirmUploadSettings,Upload Pictures To:,
CWConfirmText,"Please confirm that you would like to save this blog.",
CWConfirmText,Please confirm that you would like to save this blog.,
CWConfirmText2,"Writer will periodically check for, and download, new configuration information for your blog.",
WeblogNameColon,Blog &nickname:,
CWConfirmThanks,Your blog has been set up,
@ -408,8 +452,8 @@ CWWelcomeClickNext,Click Next to continue.,
CWWelcomeCaption,Welcome to the world of blogging!,
CWWelcomeAlreadyHave,I &already have a blog set up,
CWWelcomeText,"Use {0} to blog about whatever interests you. Add photos and videos, and then publish to popular blogging services, including WordPress, Blogger, and TypePad.",{0} - Open Live Writer
CWWelcomeCreate,"&Create a new blog",
CWWelcomeWP,"&WordPress",
CWWelcomeCreate,&Create a new blog,
CWWelcomeWP,&WordPress,
OpenPostOpenFrom,&Open from:,
OpenPostItems,items,
OpenPostRefresh,&Refresh,
@ -425,8 +469,8 @@ PagesLower,pages,
PostsLower,posts,
AboutAbout,Abou&t {0},{0} - Open Live Writer Beta
AboutVersion,{0},"{0} - version number, e.g. 2.0.1"
AboutCopyright,© 2015 .NET Foundation. All rights reserved.,
AboutConfigurationVersion,Configuration Version: {0},Displays the version of Writer's configuration file in the about dialog,
AboutCopyright,Copyright © .NET Foundation. All rights reserved.,
AboutConfigurationVersion,Configuration Version: {0},Displays the version of Writer's configuration file in the about dialog
ProductDisplayVersion,Build {0},{0}- the build number like 12.00.1234.1234.
ProductNameVersioned,Open Live Writer,Versioned product name for about dialog.
LinkEditHyperlink,Edit Hyperlink,
@ -451,13 +495,13 @@ ImgSBLocalImage,Local: {0},"{0} - a file size (e.g. localized equivalents of ""1
ImgSBLocalImage2,Local: {0}/{1},"{0}, {1} - file size (e.g. localized equivalents of ""10.2MB"", ""108 bytes"", etc.). The {0} value will be for the inline image, the {1} value will be for the ""View Larger"" image."
Post,Post,
Posts,&Posts,
RecentPosts,Recent posts, Category description for recent posts that appears in Writer's taskbar jumplist.
RecentDrafts,Recent drafts, Category description for recent drafts that appears in Writer's taskbar jumplist.
RecentPosts,Recent posts,Category description for recent posts that appears in Writer's taskbar jumplist.
RecentDrafts,Recent drafts,Category description for recent drafts that appears in Writer's taskbar jumplist.
Page,Page,
Pages,P&ages,
Blog,Blog,
Date,Date,
OpenPostNoDraftsAvailable, No drafts available.,
OpenPostNoDraftsAvailable,No drafts available.,
OpenPostNoPostsAvailable,No posts available.,
OpenPostNoPagesAvailable,No pages available.,
ShowAllPosts,All,
@ -466,14 +510,14 @@ InsertImageInsertFromFile,From File,
InsertImageInsertFromWeb,From Web,
InsertImageLinkTo,&Link to:,
InsertImageOpenInNewWindow,&Open in new window,
LinkToSource,Source picture,Navigate to the source picture when the selected image is clicked.,
LinkToSourceLabelTitle,Link to: Source picture,Shows the user that the selected image links to the original image,
LinkToSource,Source picture,Navigate to the source picture when the selected image is clicked.
LinkToSourceLabelTitle,Link to: Source picture,Shows the user that the selected image links to the original image
LinkToSourceDescription,Navigate to the source picture when the selected image is clicked.,
LinkToURL,Web address,Choose the location to go to when the selected image is clicked.,
LinkToURLLabelTitle,Link to: Web address,Shows the user that the selected image links to a URL,
LinkToURL,Web address,Choose the location to go to when the selected image is clicked.
LinkToURLLabelTitle,Link to: Web address,Shows the user that the selected image links to a URL
LinkToURLDescription,Choose the location to go to when the selected image is clicked.,
LinkToNone,None,Make the selected image not be a link.,
LinkToNoneLabelTitle,Link to: None,Shows the user that the selected image is not a link,
LinkToNone,None,Make the selected image not be a link.
LinkToNoneLabelTitle,Link to: None,Shows the user that the selected image is not a link
LinkToNoneDescription,Make the image not be a link.,
ImagesFilterString,"All pictures (*.gif, *.jpg, *.jpeg, *.png)",
AllFilesFilterString,All files (*.*),
@ -491,7 +535,8 @@ Size,Size,
TableRowsLabel,&Rows:,
TableColumnsLabel,&Columns:,
Appearance,Appearance,
pixels,pixels,
Pixels,pixels,
Percent,percent,
TableShowBorderLabel,Show table &border:,
TableCellSpacingLabel,&Space between cells:,
TableCellPaddingLabel,&Pad cell contents:,
@ -551,7 +596,7 @@ ImageEffectsNoRecolorCategory,No recolor,
ImageEffectsColorModesCategory,Color modes,
ImageEffectsColorTemperatureCategory,Color temperature,
ImageEffectsNoSharpenCategory,No sharpen,
ImageEffectsSharpenCategory,Sharpen
ImageEffectsSharpenCategory,Sharpen,
ImageEffectsNoBlurCategory,No blur,
ImageEffectsGaussianCategory,Gaussian blur,
ImageEffectsNoEmbossCategory,No emboss,
@ -568,30 +613,30 @@ DecoratorNoBorder,None,
DecoratorGroupOverlays,Overlays,
DecoratorWatermark,Watermark...,
DecoratorGroupTransformations,Transformations,
DecoratorNoRecolorLabel,No recolor,Remove any black and white or sepia applied on the selected image.,
DecoratorNoRecolorLabel,No recolor,Remove any black and white or sepia applied on the selected image.
DecoratorNoRecolorDescription,Remove recolor effects applied on the selected image.,
DecoratorBWLabel,Black and white,Apply a black and white filter on the selected image.,
DecoratorBWLabel,Black and white,Apply a black and white filter on the selected image.
DecoratorBWDescription,Black and white,
DecoratorSepiaLabel,Sepia tone,Apply a sepia filter on the selected image.,
DecoratorSepiaLabel,Sepia tone,Apply a sepia filter on the selected image.
DecoratorSepiaDescription,Sepia,
DecoratorCoolestTemperatureLabel,Coolest,Dark blue temperature effect available to apply to the selected image.
DecoratorCoolTemperatureLabel,Cool,Blue temperature effect available to apply to the selected image.
DecoratorWarmTemperatureLabel,Warm,Yellow temperature effect available to apply to the selected image.
DecoratorWarmestTemperatureLabel,Warmest,Orange temperature effect available to apply to the selected image.
DecoratorTemperatureDescription,Change the warmth of the colors in the selected image.,
DecoratorSaturationLabel,Color pop,Make the colors in the selected image vivid.,
DecoratorSaturationLabel,Color pop,Make the colors in the selected image vivid.
DecoratorSaturationDescription,Make the colors in the selected image vivid.,
DecoratorSharpenLabel,Sharpen,Apply a sharpening filter on the selected image.,
DecoratorSharpenLabel,Sharpen,Apply a sharpening filter on the selected image.
DecoratorSharpenDescription,Apply a sharpening filter on the selected image.,
DecoratorNoSharpenLabel,No sharpen,Remove the sharpening filter on the selected image.,
DecoratorNoSharpenLabel,No sharpen,Remove the sharpening filter on the selected image.
DecoratorNoSharpenDescription,Remove the sharpening filter on the selected image.,
DecoratorGaussianBlurLabel,Gaussian blur,Apply a Gaussian blur filter on the selected image.,
DecoratorGaussianBlurLabel,Gaussian blur,Apply a Gaussian blur filter on the selected image.
DecoratorGaussianBlurDescription,Apply a Gaussian blur filter on the selected image.,
DecoratorNoBlurLabel,No blur,Remove the blur filter on the selected image.,
DecoratorNoBlurLabel,No blur,Remove the blur filter on the selected image.
DecoratorNoBlurDescription,Remove the blur filter on the selected image.,
DecoratorEmbossLabel,Emboss,Apply an emboss effect on the selected image.,
DecoratorEmbossLabel,Emboss,Apply an emboss effect on the selected image.
DecoratorEmbossDescription,Apply an emboss effect on the selected image.,
DecoratorNoEmbossLabel,No emboss,Remove the emboss effect on the selected image.,
DecoratorNoEmbossLabel,No emboss,Remove the emboss effect on the selected image.
DecoratorNoEmbossDescription,Remove the emboss effect on the selected image.,
ImgSBMaximumWidthLabel1,Maximum &Width:,
ImgSBMaximumHeightLabel1,Maximum &Height:,
@ -641,7 +686,7 @@ TagsHtmlDelimiterHelpString,The separator that should appear between tag HTML. T
TagsProviderNameLabel,&Provider name:,
TagsProviderNameHelpString,The name of the tag provider. This name will be displayed in the tag provider list when inserting tags.,
TagsHtmlCaptionLabel,HTML &caption for tag list:,
TagsHtmlCaptionHelpString,The HTML caption that will appear with the HTML generated for the tags. Use {0} where you'd like the HTML generated from the HTML tag template to be placed.,"{0} - ""{tag-group}"""
TagsHtmlCaptionHelpString,The HTML caption that will appear with the HTML generated for the tags. Use {0} where you'd like the HTML generated from the HTML tag template to be placed.,{0} - "{tag-group}"
TagsHtmlPreviewLabel,HTML preview:,
TagsHtmlPreviewHelpString,A preview of the literal HTML that will be generated for a set of tags.,
TagsCreateNew,Create New Tag Provider,
@ -649,8 +694,8 @@ TagsProviderDefaultCaption,{0} Tags: {1},"{0} - the name of a tag provider, {1}
TagsCaptionFormat,{0} Tags: {{tag-group}},{0} - the name of a tag provider. {{tag-group}} must NOT be translated!
Tags,Tags,
TitleDefaultText,Enter a {0} title,{0} - Post or Page
SpinnerPixelRepresentativeString,999 px,The Ribbon uses the width of this string to decide how wide the spinner should be displayed,
SpinnerPixelFormatString," px",Unit of measurement (pixels) that is appended to the width and height of an image (e.g. 400 px) in the ribbon spinners,
SpinnerPixelRepresentativeString,999 px,The Ribbon uses the width of this string to decide how wide the spinner should be displayed
SpinnerPixelFormatString,px,Unit of measurement (pixels) that is appended to the width and height of an image (e.g. 400 px) in the ribbon spinners
MapSBCaption,Caption:,
MapSBMapHeader,Map,
MapSBMargins,Margins:,
@ -664,7 +709,6 @@ MapSBViewMap,View Map on Live.com,
MapSBCustomize,Customize Map...,
MapSBCustomizeTooltip,"Edit the map location, style, and pins",
DownloadingWeblogStyle,Downloading Blog Theme,
PasteSpecialPlaintextLabel,&Remove Formatting,
PasteSpecialPlaintextDesc,Removes all formatting except for line breaks. Preserves links and pictures. Use to copy content only.,
PasteSpecialThinnedLabel,&Thinned HTML,
@ -728,7 +772,7 @@ CategoryNoParent,(No parent),Indicates that a category does not have a parent ca
AddButton2,&Add,
CategoryRefreshList,Refresh List,
CategoryRefreshListTooltip,Refreshes the list of categories.,
CategoryCategoryName,Category Name,The 'cue banner' for the textbox in the category popup, where you type in a new category name.
CategoryCategoryName,Category Name,The 'cue banner' for the textbox in the category popup
ColorPickerDefaultColor,&Default Color,
ColorPickerMoreColors,&More Colors...,
ColorWhite,White,
@ -760,12 +804,12 @@ ColorPastelOrange,Pastel orange,
ColorPastelYellow,Pastel yellow,
ColorPastelGreen,Pastel green,
ColorPastelBlue,Pastel blue,
ColorPastelPurple,Pastel purple,
ColorPastelPurple,Pastel purple,
Untitled,Untitled,
WindowTitleFormat,{0} - {1},"{0} - post title, {1} - ""Open Live Writer"""
StatusDraftUnsaved,Draft - Unsaved,
StatusDraftSaved,Draft - Saved {0},{0} - saved date
StatusAutoSaving,Autosaving...,This message is shown in the status bar while Writer saves the current post in the background,
StatusAutoSaving,Autosaving...,This message is shown in the status bar while Writer saves the current post in the background
StatusPublished,Published: {0},{0} - publish date
StatusWeblogNameFormatter,{0} ({1}),"{0} - status message, {1} - blog name"
ChangeLiveClipboardHandlerComponent,Component,
@ -837,7 +881,7 @@ PostEditorStorageExceptionTitle,Error Accessing Local Post,
PostEditorStorageExceptionMessage,"Unexpected error occurred while accessing local post ({0})
{1}","{0} - error code, {1} - error message"
PostEditorDiskSpaceExceptionMessage,"No more space is available on disk."
PostEditorDiskSpaceExceptionMessage,No more space is available on disk.,
PostEditorStorageExceptionTitle2,Error Accessing Local Post,
PostEditorStorageExceptionMessage2,"Unexpected disk access error occurred while accessing local post ({0})
@ -849,6 +893,7 @@ UnexpectedErrorContinue,&Continue,
UnexpectedErrorClickHere,Click here to see error details,
UnexpectedErrorDescription,We have created an error summary that you can view.,
UnexpectedErrorTitle,Unexpected Error,
UnexpectedErrorSendError,&Send Error,
UnexpectedErrorPluginTitle,Plug-in Error Occurred,
UnexpectedErrorPluginDescription,"An error occurred in the {0} plug-in:
@ -857,8 +902,8 @@ UnhandledExceptionErrorMessage,An unexpected error has occurred within the appli
UnhandledExceptionErrorTitle,Unexpected Error Occurred,
PassportLoginSignIn,Sign in,
PassportLoginTitle,Sign in to Microsoft Account,
PassportLoginNoUserPass,Please enter a Microsoft Account ID and password to continue.,
PassportLoginNoUserPassTitle,Microsoft Account ID and Password Required
PassportLoginNoUserPass,Please enter a Microsoft Account and password to continue.,
PassportLoginNoUserPassTitle,Microsoft Account and Password Required,
MinimizeButtonTooltip,Minimize,
MaximizeButtonTooltip,Maximize,
CloseButtonTooltip,Close,In the sense of closing a window.
@ -945,13 +990,13 @@ Today,Today,
SidebarPanel,Taskpane,This is an accessibility name and should be localized.
PluginSidebarNotEditable,Selected item cannot be edited.,
PluginSidebarDisabled,Selected item plug-in is disabled.,
PublishNow,"Publish now",
FuturePostWarningDialogTitle,"Open Live Writer",
PublishNow,Publish now,
FuturePostWarningDialogTitle,Open Live Writer,
FuturePostWarningDontShowAgain,&Always publish immediately without asking again,
FuturePostWarningExplanation,"This blog service can't delay publishing for the date that you set. Would you like to publish it now?",
FuturePostWarningExplanation,This blog service can't delay publishing for the date that you set. Would you like to publish it now?,
FuturePostWarningTitle,Can't set a future date for publishing,
LinkToolTip,CTRL + click to follow link: {0},Used when hovering over a link in the editor. {0} - the hyperlink to follow
SplashScreenCopyrightNotice,© 2015 .NET Foundation. All rights reserved.,
SplashScreenCopyrightNotice,Copyright © .NET Foundation. All rights reserved.,
BlogCategoryNone,None,Allows the user to specify that the blog post has no categories assigned to it.
CWSelectProviderWeblogProvider,Blog Provider,
CWSelectProviderApiUrl,Server API web address,
@ -965,7 +1010,7 @@ PostLinkTooltipFormat,"{0}
{1} ({2})","{0} - title of post, {1} - title of blog, {2} - date/time of post"
PostLinkTooltipFormatNoBlogTitle,"{0}
({1})","{0} - title of post, {1} - date/time of post"
DraftNameFormat,Draft {0},"{0} - Post or Page. ""Draft"" is an adjective in this context"
DraftNameFormat,Draft {0},{0} - Post or Page. "Draft" is an adjective in this context
PublishedNameFormat,Published {0},{0} - Post or Page
PostPublishFileUploadErrorTitle,Error Uploading Supporting Files,
PostPublishFileUploadErrorCaption1,The {0} was successfully published however an error occurred while uploading supporting files:,{0} - Post or Page
@ -1014,9 +1059,9 @@ CapabilityTemplateIsRTL,Template is Right-to-Left,This is used in the Blog Optio
CapabilityCategoryNameLimit,Category Name Limit,This is used in the Blog Options | Blog Capabilities dialog.
CapabilityPostTitleLengthLimit,Title Length Limit,This is used in the Blog Options | Blog Capabilities dialog.
CapabilityAutoUpdate,Auto-Update,This is used in the Blog Options | Blog Capabilities dialog.
CapabilityValueNormal,Normal,"This is used in the Blog Options | Blog Capabilities dialog. It is one of the possible values for ""Default View""."
CapabilityValueWebLayout,Web Layout,"This is used in the Blog Options | Blog Capabilities dialog. It is one of the possible values for ""Default View""."
CapabilityValueWebPreview,Web Preview,"This is used in the Blog Options | Blog Capabilities dialog. It is one of the possible values for ""Default View""."
CapabilityValueNormal,Normal,This is used in the Blog Options | Blog Capabilities dialog. It is one of the possible values for "Default View".
CapabilityValueWebLayout,Web Layout,This is used in the Blog Options | Blog Capabilities dialog. It is one of the possible values for "Default View".
CapabilityValueWebPreview,Web Preview,This is used in the Blog Options | Blog Capabilities dialog. It is one of the possible values for "Default View".
CapabilityValueNoLimit,(No Limit),Specifies that there is no limit to the number of characters in a category name.
SelectWeblogProvider,(Select Blog Provider),"This appears in the drop-down list with blog providers, hence the parentheses."
Comma,",","The comma character for this language. (e.g., U+FF0C for Chinese)"
@ -1032,7 +1077,7 @@ BetaExpiredDownloadNow,&Download Now,Text displayed on download button of beta e
BetaExpiredAskLater,&Ask Me Later,Text displayed on ask me later button of beta expiration warning dialog
UpdatesAvailableMessage,Required updates will be applied next time you start Open Live Writer.,Message shown to user when updates are found.
UpdatesBandClose,Close,Used to hide the updates available message
UpdatesPreparing,Preparing to update Open Live Writer,Message to user while updates are getting downloaded.
UpdatesPreparing,Preparing to update Open Live Writer...,Message to user while updates are getting downloaded.
UpdatesInstalling,Downloading and installing {0} update(s). Please wait...,{0} is number of updates
UpdatesStartingWriter,Starting updated Open Live Writer...,Message after updates are installed and before writer starts.
BloggerImageAlbumDescription,This album is used to store pictures from blog posts published by Open Live Writer.,
@ -1098,18 +1143,18 @@ Plugin_Video_Soapbox_Soap_Error,The Soapbox service returned an error.,Message w
Plugin_Video_Soapbox_Thumbnail_Downloading,Downloading Preview...,Message shown for each video while thumbnail is getting generated.
Plugin_Video_Soapbox_Thumbnail_None,No Preview Available,Message shown for each video that doesn't have a thumbnail.
YouTubeInvalidResult,An unexpected response from YouTube was received.,
YouTubecopyright,The video commits a copyright infringement.
YouTubeinappropriate,The video contains inappropriate content.
YouTubeduplicate,The video is a duplicate of another uploaded video.
YouTubetermsOfUse,The video commits a terms of use violation.
YouTubesuspended,The account associated with the video has been suspended.
YouTubetooLong,The video exceeds the maximum duration of 10 minutes.
YouTubeblocked,The video has been blocked by the content owner.
YouTubecantProcess,YouTube is unable to convert the video file.
YouTubeinvalidFormat,The uploaded video is in an invalid file format.
YouTubeunsupportedCodec,The video uses an unsupported codec.
YouTubeempty,The uploaded file is empty.
YouTubetooSmall,The uploaded file is too small.
YouTubecopyright,The video commits a copyright infringement.,
YouTubeinappropriate,The video contains inappropriate content.,
YouTubeduplicate,The video is a duplicate of another uploaded video.,
YouTubetermsOfUse,The video commits a terms of use violation.,
YouTubesuspended,The account associated with the video has been suspended.,
YouTubetooLong,The video exceeds the maximum duration of 10 minutes.,
YouTubeblocked,The video has been blocked by the content owner.,
YouTubecantProcess,YouTube is unable to convert the video file.,
YouTubeinvalidFormat,The uploaded video is in an invalid file format.,
YouTubeunsupportedCodec,The video uses an unsupported codec.,
YouTubeempty,The uploaded file is empty.,
YouTubetooSmall,The uploaded file is too small.,
YouTubeVideoError,There was an unexpected error while uploading the video.,
VideoGetVideosError,There was an error getting the list of videos.,
VideoNetworkError,There was an error with the network connection.,
@ -1119,7 +1164,7 @@ VideoRemoteProcessing,"{0} is processing your uploaded video.
This might take a while.",{0} - Soapbox or Youtube
VideoUrlConvertError,This video does not contain the video ID. Please enter the embed for the video.,
InsertImageDimensionsFormat,{0}x{1},"{0} - width, {1} - height. Example: 800x600"
InsertImageDimensionsFormatScaled,{0} (resized for preview in this window),"{0} - original dimensions"
InsertImageDimensionsFormatScaled,{0} (resized for preview in this window),{0} - original dimensions
TemporaryPostTitle,Temporary Post Used For Theme Detection ({0} - {1}),"{0} and {1} - a machine-readable value, for example, B8E2D78C-CD8C-4235-A77E-45F51248128E"
TemporaryPostBody,This is a temporary post that was not deleted. Please delete this manually. ({0} - {1}),"{0} and {1} - a machine-readable value, for example, B8E2D78C-CD8C-4235-A77E-45F51248128E"
Plugin_Video_Soapbox_Not_Logged_In,Not signed in,Shown when user is not currently logged in to Passport.
@ -1135,7 +1180,7 @@ ToolbarFontStyleFontFamily,,The typeface that should be used to render the Bold
Plugin_Video_Publish_Message,Waiting for videos to finish publishing,
VideoError,Error Publishing Video,
VideoErrorTryAgain,Please remove the video and try to publish again.,
Plugin_Video_Publish_Filename_Seperator,_,In English people often use _ to separate words in a file name. For example I might name a video file My_Birthday_Party.wmv. We will use this string to replace the char with a space. If this can't be applied to a certain language, then simple make it an empty string and it will be ignored.
Plugin_Video_Publish_Filename_Seperator,_,In English people often use _ to separate words in a file name. For example I might name a video file My_Birthday_Party.wmv. We will use this string to replace the char with a space. If this can't be applied to a certain language
Plugin_Video_Soapbox_Publish_Video_Open_File,Open Video File,
Plugin_Video_Soapbox_Publish_Video_File,Video &File:,
Plugin_Video_Soapbox_Publish_Title,&Title:,
@ -1184,51 +1229,13 @@ ShowLogFile,Show log file,Appears in Help | About dialog
ChangeTextStyle,Change Text Style,Tooltip for style switcher in editing toolbar
Plugin_Video_Soapbox_LoggingIn,Signing in...,Status message shown during login process
Plugin_Video_Alt_Text,Video,Alt text in image tag when inserting a video
Plugin_Video_Soapbox_Total_Videos,(of {0}),"used in pagination ie ""Videos 1 - 10 (of 12)"" this is the part in the paren. With the {0} being the number of videos."
Plugin_Video_Soapbox_Total_Videos,(of {0}),used in pagination ie "Videos 1 - 10 (of 12)" this is the part in the paren. With the {0} being the number of videos.
Plugin_Video_Editor_Caption,Caption:,Caption for video
Plugin_Video_Caption_Size,.8em,"The size of the font used for the video caption feature. Only localize if the font size must be changed. Use format number.em (i.e. 1em, .9em, etc..)"
SplitterMore,More...,
Plugin_Video_Provider_Select,Select Video Provider,Accessible name for video provider service (Soapbox or YouTube Videos)
DictionaryLanguageLabel,Dictionary &language:,
DictionaryLanguageNone,(None),
DictionaryLanguageEnglishUS,English (United States),
DictionaryLanguageEnglishUK,English (United Kingdom),
DictionaryLanguageEnglishCanada,English (Canada),
DictionaryLanguageEnglishAustralia,English (Australia),
DictionaryLanguageSpanish,Spanish,
DictionaryLanguageGerman,German,
DictionaryLanguageGermanReform,German (Reform),
DictionaryLanguageFrench,French,
DictionaryLanguageKorean,Korean,
DictionaryLanguageItalian,Italian,
DictionaryLanguageDutch,Dutch,
DictionaryLanguagePortugueseBrazil,Portuguese (Brazil),
DictionaryLanguageTurkish,Turkish,
DictionaryLanguageArabic,Arabic,
DictionaryLanguageBulgarian,Bulgarian,
DictionaryLanguageCatalan,Catalan,
DictionaryLanguageCzech,Czech,
DictionaryLanguageGreek,Greek,
DictionaryLanguageEstonian,Estonian,
DictionaryLanguageBasque,Basque,
DictionaryLanguageFinnish,Finnish,
DictionaryLanguageHebrew,Hebrew,
DictionaryLanguageCroatian,Croatian,
DictionaryLanguageHungarian,Hungarian,
DictionaryLanguageIndonesian,Indonesian,
DictionaryLanguageLithuanian,Lithuanian,
DictionaryLanguageLatvian,Latvian,
DictionaryLanguageMalay,Malay,
DictionaryLanguagePolish,Polish,
DictionaryLanguagePortuguese,Portuguese,
DictionaryLanguageRomanian,Romanian,
DictionaryLanguageRussian,Russian,
DictionaryLanguageSlovak,Slovak,
DictionaryLanguageSlovenian,Slovenian,
DictionaryLanguageSerbianLatin,Serbian (Latin),
DictionaryLanguageSerbianCyrillic,Serbian (Cyrillic),
DictionaryLanguageSwedish,Swedish,
DictionaryLanguageUkrainian,Ukrainian,
CapabilityCategoryScheme,Category Scheme,
Plugin_Video_Copyright_Notice,Please respect copyright,"Text shown on insert video dialog to remind users of copyright responsibilities. For some markets, will be linked."
SwitchWeblogs,Switch to Blog,
@ -1243,14 +1250,14 @@ SendingPing,Sending ping,
SendingPings,Sending pings,
GlossaryAutomaticallyLink,A&utomatically link matched terms,
GlossaryAutomaticallyLinkFirstTime,Li&nk to each term only once per post,
GlossaryAutomaticLinkOptions,"Automatic linking options",
GlossaryExampleTitle,"Open Live Writer site",This is used in the link glossary as the title of our example link- may not need to be localized
GlossaryAutomaticLinkOptions,Automatic linking options,
GlossaryExampleTitle,Open Live Writer site,This is used in the link glossary as the title of our example link- may not need to be localized
AutoreplaceTypographic,Replace h&yphens (--) with dash (—),
AutoreplacePrefererencesPanel,Auto Replace,"Currently not in use- do not localize"
AutoreplacePrefererencesPanel,Auto Replace,Currently not in use- do not localize
AutoreplaceSmartQuotes,Replace "straight quotes" with “smart &quotes”,
AutoreplaceOtherChars,Re&place other special characters,
AutoreplaceFormTitle,Auto Replace,"Currently not in use- do not localize"
AutoreplaceWordorPhrase,Word or phrase:,"Currently not in use- do not localize"
AutoreplaceFormTitle,Auto Replace,Currently not in use- do not localize
AutoreplaceWordorPhrase,Word or phrase:,Currently not in use- do not localize
AutoreplaceReplacementValue,Replacement value:,'"Currently not in use- do not localize"
AutoreplaceEmoticons,Replace text &emoticons with emoticon graphics,
WordCount,Words,{0} - The number of words in any given text.
@ -1274,40 +1281,40 @@ CropAspect35x5,3.5x5,
CropAspectSquare,Square,
CropDialogTitle,Crop,
WatermarkDialogTitle,Watermark,
AltTextEditorTitle,Alt-text,Title of the HTML image alternate text dialog,
AltTextEditorTitle,Alt-text,Title of the HTML image alternate text dialog
CropAspectRatioLabel,&Proportion:,
CropRotateFrame,Rotate &Frame,Command to rotate by 90 degrees the frame that indicates the cropping area
CropRemoveCrop,&Remove Crop,Remove the crop that is currently applied to the image
CropShowGridlines,Show &Grid,Checkbox to show two horizontal and two vertical lines within the cropping rectangle
ImageViewerDisplayFormatSingle,Using {0},"{0} - the name of the image viewer (e.g. Lightbox)"
ImageViewerDisplayFormatSingle,Using {0},{0} - the name of the image viewer (e.g. Lightbox)
ImageViewerDisplayFormatGroup,Using {0} [{1}],"{0} - the name of the image viewer (e.g. Lightbox), {1} - the name of the group. For example, ""Lightbox [Ski Trip]""."
ImageViewerLabel,&Enable {0},{0} - the name of the image viewer (e.g. Lightbox)
ImageViewerGroupLabel,&Group (optional):,A Group is a label that allows multiple images to be grouped into a set
PrivacyPreferencesPanelName,Trust Center,
PrivacyPreferencesPrivacyExplanation,"Your privacy is important. For more information about how Open Live Writer helps to protect it, see the:","Three links follow the colon: Microsoft Privacy statement, Microsoft service agreement and Code of Conduct",
PrivacyPreferencesPrivacyStatement,".NET Foundation Privacy Policy",
PrivacyPreferencesCodeOfConduct,".NET Foundation Contributor Code of Conduct",
TiltEditorTitle,"Tilt",
TiltEditorLabel,"&Tilt:",
MethodNotImplemented,"Method Not Implemented",Title of plug-in error dialog
PrivacyPreferencesPrivacyExplanation,"Your privacy is important. For more information about how Open Live Writer helps to protect it, see the:","Three links follow the colon: Privacy statement, service agreement and Code of Conduct"
PrivacyPreferencesPrivacyStatement,.NET Foundation Privacy Policy,
PrivacyPreferencesCodeOfConduct,.NET Foundation Contributor Code of Conduct,
TiltEditorTitle,Tilt,
TiltEditorLabel,&Tilt:,
MethodNotImplemented,Method Not Implemented,Title of plug-in error dialog
MethodNotImplementedDetail,"An installed plugin is incorrectly implemented and has caused an error. You may be able to contact the plugin's publisher below to resolve this issue. Alternatively, you may avoid this error in the future by uninstalling the plugin mentioned below.
Name: {0}
Publisher: {1}
Unimplemented Method: {2}",More detailed header in plug-in error dialog
UpdatesAvailableTitle,"New Version Available",
UpdatesAvailableTitlePrerelease,"New Pre-Release Version Available",
UpdatesAvailableMessagePrerelease,"A new pre-release version of {0} is now available. Click the download now button to download and install the update.",{0} will be replaced with the product name (i.e. Open Live Writer)
UpdatesAvailableMessageFull,"A new version of {0} is now available. Click the download now button to download and install the update.",{0} will be replaced with the product name (i.e. Open Live Writer)
UpdatesAvailableLink,"More information",
UpdatesAvailableNotify,"&Automatically check for updates",
UpdatesAvailableNofifyBeta,"Also check for pre-release (beta) &versions",
UpdatesAvailableTitle,New Version Available,
UpdatesAvailableTitlePrerelease,New Pre-Release Version Available,
UpdatesAvailableMessagePrerelease,A new pre-release version of {0} is now available. Click the download now button to download and install the update.,{0} will be replaced with the product name (i.e. Open Live Writer)
UpdatesAvailableMessageFull,A new version of {0} is now available. Click the download now button to download and install the update.,{0} will be replaced with the product name (i.e. Open Live Writer)
UpdatesAvailableLink,More information,
UpdatesAvailableNotify,&Automatically check for updates,
UpdatesAvailableNofifyBeta,Also check for pre-release (beta) &versions,
UpdatesAvailableDownloadButton,&Download Now,Text displayed on download button
UpdatesAvailableAskLaterButton,A&sk Me Later,Text displayed on ask me later button
UpdatesAvailableNoUpdateTitle,No Updates Available,
UpdatesAvailableChecking,Checking for updates,Displayed in progress dialog while user manually checks for updates
UpdatesAvailableNoUpdateText,There are no updates available. You are running the latest version of {0}.,"{0} will be replaced with the product name"
UpdatesAvailableNoUpdateText,There are no updates available. You are running the latest version of {0}.,{0} will be replaced with the product name
BlogPluginsDescription,"Select which plug-ins you want to use, and change the order in which they start.",From the Weblog | Edit Weblog Settings | Plugins preferences panel
MoveUp,Move &Up,From the Weblog | Edit Blog Settings | Plugins preferences panel
MoveDown,Move &Down,From the Weblog | Edit Blog Settings | Plugins preferences panel
@ -1319,9 +1326,9 @@ PPCRL_REQUEST_E_PASSWORD_LOCKED_OUT,The user account has been blocked.,
PPCRL_REQUEST_E_CLIENT_DEPRECATED,The client issuing this request is no longer supported by the login server. Please upgrade to a newer version.,
PPCRL_REQUEST_E_CANCELLED,The request was cancelled.,
PPCRL_REQUEST_E_FORCE_SIGNIN,The authentication token has expired.,
WatermarkDefaultFont,Arial,"The default font that should be selected in the watermark editor"
DefaultTemplateBodyFont,"Calibri, Trebuchet MS, Helvetica, Arial, Sans-Serif",The font used for the post body when editing using no theme. Should be a comma separated list of HTML supported font names, e.g. Tahoma, Arial, Times New Roman.
DefaultTemplateTitleFont,"Calibri, Trebuchet MS, Helvetica, Arial, Sans-Serif",The font used for the post title when editing using no theme. Should be a comma separated list of HTML supported font names, e.g. Tahoma, Arial, Times New Roman.
WatermarkDefaultFont,Arial,The default font that should be selected in the watermark editor
DefaultTemplateBodyFont,"Calibri, Trebuchet MS, Helvetica, Arial, Sans-Serif",The font used for the post body when editing using no theme. Should be a comma separated list of HTML supported font names
DefaultTemplateTitleFont,"Calibri, Trebuchet MS, Helvetica, Arial, Sans-Serif",The font used for the post title when editing using no theme. Should be a comma separated list of HTML supported font names
VideoFromVideoService,From Video Service,The name of the tab in the insert video dialog that allows users to insert soapbox or youtube videos.
VideoCaptionMaxCharactersAccepted,245,The maximum number of characters allowed in the video caption edit field - Cannot be greater than 245
VideoCaptionDefaultText,Enter video caption here,
@ -1329,17 +1336,17 @@ MyVideos,My Videos,Videos uploaded by current user
MyFavorites,My Favorites,Videos marked as favorites by current user
ManageWeblog,Manage Blog,Displayed in the header of the default sidebar
PhotoPreview,Inline Photo Previewer,
PostPageFilter,Filter {0}, {0} - Posts or Pages
PostPageFilter,Filter {0},{0} - Posts or Pages
LiveIDPrivacy,Privacy policy,
ForgotMyPassword,Forgot my password,
BrowseForFile,Browse for file,Tooltip over the button in the insert video from file dialog that opens an Open File Dialog when clicked,
BlogServiceNames,"TypePad and more",
BrowseForFile,Browse for file,Tooltip over the button in the insert video from file dialog that opens an Open File Dialog when clicked
BlogServiceNames,TypePad and more,
FindCategory,Find category,
Alignment,Alignment,Tooltip for image alignment dropdown,
Alignment,Alignment,Tooltip for image alignment dropdown
CropPane,Image crop pane,
PostCountAccessible,Number of posts,Accessible name for a combo box in the Open Post form that lets the user filter how many posts they want to show up in the search,
PostCountAccessible,Number of posts,Accessible name for a combo box in the Open Post form that lets the user filter how many posts they want to show up in the search
ColorPickerColor,Color,
CategoryParentAccessible,Category parent,Accessible name for a combo box that lets you choose the parent category for a new blog category,
CategoryParentAccessible,Category parent,Accessible name for a combo box that lets you choose the parent category for a new blog category
MaxSmHeight,Maximum small height,
MaxMdHeight,Maximum medium height,
MaxLgHeight,Maximum large height,
@ -1348,7 +1355,7 @@ MaxMdWidth,Maximum medium width,
MaxLgWidth,Maximum large width,
WatermarkDefaultText,© {0:yyyy},{0} - Year. This string can be blank. THis is the default text in the watermark textbox when the image has no watermark
AutoRecoverDialogInstruction,Recover unsaved changes?,
AutoRecoverDialogContent,"{0} did not shut down properly the last time you used it. Unsaved changes are available for recovery. Would you like to recover these changes now?",{0} - Open Live Writer
AutoRecoverDialogContent,{0} did not shut down properly the last time you used it. Unsaved changes are available for recovery. Would you like to recover these changes now?,{0} - Open Live Writer
AutoRecoverDialogButtonRecover,&Recover changes now (recommended),
AutoRecoverDialogButtonDiscard,&Discard changes permanently,
AutoRecoverDialogButtonAskLater,&Ask me later,
@ -1356,7 +1363,78 @@ Selected,Selected,Used for the Accessible Value of the currently selected view s
CopyrightInformation,Copyright Information,This is the string that will be read by screen reader to announce the copyright legal information on the about dialog.
ViewAll,View all,shows the full list of options
SeeSolutionOnline,See the solution online,Dialog box that gives an error while signing in to Live ID. This string prompts user to go online to resolve the problem.
CantSignIn,Can't sign in because an error occurred
TheErrorOccuredBecause,The error occurred for the following reason:
RetrySignin,"After you go through the solution, try signing in again."
GenericLoginError,Unable to login to Live ID.
CantSignIn,Can't sign in because an error occurred,
TheErrorOccuredBecause,The error occurred for the following reason:,
RetrySignin,"After you go through the solution, try signing in again.",
GenericLoginError,Unable to login to Live ID.,
SSGErrorPostDoesNotExistTitle,Post does not exist,
SSGErrorPostDoesNotExistText,Could not find post with specified ID.,
SSGErrorPageDoesNotExistTitle,Page does not exist,
SSGErrorPageDoesNotExistText,Could not find page with specified ID.,
SSGErrorCommandTimeoutTitle,Command execution timeout,
SSGErrorCommandTimeoutText,"Blog command timed out. Please check your commands, or lengthen the command timeout.",
SSGErrorPathFolderNotFound,Folder not found,
SSGErrorPathLocalSitePathNotFound,Local site path '{0}' does not exist.,{0} - attempted local site path
SSGErrorPathPostsEmpty,Posts path is empty.,
SSGErrorPathPostsNotFound,Posts path '{0}' does not exist.,{0} - attempted posts path
SSGErrorPathPagesEmpty,Pages path is empty.,
SSGErrorPathPagesNotFound,Pages path '{0}' does not exist.,{0} - attempted pages path
SSGErrorPathDraftsEmpty,Drafts path is empty.,
SSGErrorPathDraftsNotFound,Drafts path '{0}' does not exist.,{0} - attempted drafts path
SSGErrorPathImagesEmpty,Images path is empty.,
SSGErrorPathImagesNotFound,Images path '{0}' does not exist.,{0} - attempted images path
SSGErrorPathOutputEmpty,Output path is empty.,
SSGErrorPathOutputNotFound,Output path '{0}' does not exist.,{0} - attempted output path
SSGErrorBuildCommandEmptyTitle,Build command empty,
SSGErrorBuildCommandEmptyText,A build command is required when local site building is enabled.,
SSGErrorPublishCommandEmptyTitle,Publish command empty,
SSGErrorPublishCommandEmptyText,A publish command is required.,
SSGErrorItemLoadTitle,Item load error,
SSGErrorItemLoadTextFM,Could not read item front matter.,
SSGErrorItemLoadTextId,Item does not have an ID.,
SSGFrontMatterId,ID,
SSGFrontMatterTitle,Title,
SSGFrontMatterDate,Date,
SSGFrontMatterLayout,Layout,
SSGFrontMatterTags,Tags,
SSGFrontMatterPermalink,Permalink,
SSGFrontMatterParentId,Parent ID,
SSGConfigTitle,Static Site Configuration for '{0}',{0} - title of blog
SSGConfigGeneralTitle,General,
SSGConfigGeneralSetupGroup,Setup,
SSGConfigGeneralSiteTitle,Site &Title:,
SSGConfigGeneralSiteUrl,Site &URL:,
SSGConfigGeneralLocalSitePath,&Local Site Path:,
SSGConfigGeneralOptionsGroup,Options,
SSGConfigGeneralWizardLabel,You can choose to run the Account Wizard again if you wish to be guided through the core static site configuration options interactively.,
SSGConfigGeneralWizardButton,Run Account &Wizard,
SSGConfigGeneralDetectLabel,"Open Live Writer can also reattempt to detect relevant configuration options for your static site. This may not result in a complete configuration, so please use the fields on the following pages to ensure all settings are set correctly.",
SSGConfigGeneralDetectButton,Run Auto-&Detect,
SSGConfigAuthoringTitle,Authoring,
SSGConfigAuthoringPostsDraftsGroup,Posts and Drafts,
SSGConfigAuthoringPostsPath,&Posts Path: (relative),
SSGConfigAuthoringEnableDrafts,Enable &Drafts,
SSGConfigAuthoringDraftsPath,Drafts Path: (relative),
SSGConfigAuthoringPagesGroup,Pages,
SSGConfigAuthoringEnablePages,Enable P&ages,
SSGConfigAuthoringPagesPath,Pages Path: (relative),
SSGConfigAuthoringPagesInRoot,Pages Stored In Project Root,
SSGConfigAuthoringImagesGroup,Images,
SSGConfigAuthoringEnableImages,Enable &Images,
SSGConfigAuthoringImagesPath,Images Path: (relative),
SSGConfigFrontMatterTitle,Front Matter,
SSGConfigFrontMatterSubtitle,Below you can adjust the post front matter keys used to match your static site generator.,
SSGConfigFrontMatterPropertyCol,Property,
SSGConfigFrontMatterKeyCol,Front Matter Key,
SSGConfigFrontMatterReset,&Reset to Defaults,
SSGConfigBuildPublishTitle,Building and Publishing,
SSGConfigBuildPublishGeneralGroup,General,
SSGConfigBuildPublishShowCmdWindows,&Show Command Windows,
SSGConfigBuildPublishEnableCmdTimeout,&Enable Command Timeout,
SSGConfigBuildPublishCmdTimeout,&Command Timeout (ms):,
SSGConfigBuildPublishBuildingGroup,Building,
SSGConfigBuildPublishEnableBuilding,Enable &Building,
SSGConfigBuildPublishBuildCommand,Build Command:,
SSGConfigBuildPublishOutputPath,Site Output Path: (relative),
SSGConfigBuildPublishPublishingGroup,Publishing,
SSGConfigBuildPublishPublishCommand,&Publish Command:,

Can't render this file because it has a wrong number of fields in line 52.

View File

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
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.-->
<!--
Microsoft ResX Schema
@ -17,14 +21,15 @@
<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="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>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
@ -1012,11 +1017,20 @@
<data name="CWConfirmWeblogName" xml:space="preserve">
<value>Blog Name</value>
</data>
<data name="CWGoogleBloggerDescription" xml:space="preserve">
<value>To configure Google Blogger please sign in.</value>
</data>
<data name="CWGoogleBloggerSignInSuccess" xml:space="preserve">
<value>Successfully signed in</value>
</data>
<data name="CWGoogleBloggerTitle" xml:space="preserve">
<value>Provide Google Blogger Login</value>
</data>
<data name="CWLiveIDCreateAccount" xml:space="preserve">
<value>If you use Hotmail, Messenger, or Xbox LIVE, you have a Microsoft Account ID.</value>
<value>If you use Hotmail, Messenger, or Xbox LIVE, you have a Microsoft Account.</value>
</data>
<data name="CWLiveIDCreateAccount2" xml:space="preserve">
<value>Don't have a Microsoft Account ID?</value>
<value>Don't have a Microsoft Account?</value>
</data>
<data name="CWProgressHeader" xml:space="preserve">
<value>Setting up your blog account</value>
@ -1066,21 +1080,94 @@
<data name="CWSharePointUseSystemLogin" xml:space="preserve">
<value>Use my &amp;Windows user name and password</value>
</data>
<data name="CWGoogleBloggerTitle" xml:space="preserve">
<value>Provide Google Blogger Login</value>
</data>
<data name="CWGoogleBloggerDescription" xml:space="preserve">
<value>To configure Google Blogger please sign in.</value>
</data>
<data name="CWGoogleBloggerSignInSuccess" xml:space="preserve">
<value>Successfully signed in</value>
</data>
<data name="CWSpacesUsername" xml:space="preserve">
<value>&amp;Microsoft Account ID:</value>
<value>&amp;Microsoft Account:</value>
</data>
<data name="CWSpacesUsernameExample" xml:space="preserve">
<value>(example555@hotmail.com)</value>
<comment>Example of Microsoft Account ID</comment>
<comment>Example of Microsoft Account</comment>
</data>
<data name="CWStaticSiteCommandsBuildCommand" xml:space="preserve">
<value>Build command:</value>
</data>
<data name="CWStaticSiteCommandsBuildCommandSubtitle" xml:space="preserve">
<value>Command that builds your site and stores the output locally, ready to be published. Required if Local Site Building enabled.</value>
</data>
<data name="CWStaticSiteCommandsPublishCommand" xml:space="preserve">
<value>Publish command:</value>
</data>
<data name="CWStaticSiteCommandsPublishCommandSubtitle" xml:space="preserve">
<value>Command that uploads your site to your hosting provider. If Local Site Building is enabled, this command would typically upload the output from the build command.</value>
</data>
<data name="CWStaticSiteCommandsSubtitle" xml:space="preserve">
<value>{0} will run the commands you specify below and alert you if an error occurs. Commands are ran using the system command interpreter with a working directory of your local site.</value>
<comment>{0} - Product name</comment>
</data>
<data name="CWStaticSiteCommandsTitle" xml:space="preserve">
<value>Provide site authoring commands</value>
</data>
<data name="CWStaticSiteConfigDetection" xml:space="preserve">
<value>{0} was able to automatically determine a partial configuration for your site. Please confirm that the pre-filled details on the next pages are correct, and complete the other missing fields.</value>
<comment>{0} - Product name</comment>
</data>
<data name="CWStaticSiteFeaturesBuilding" xml:space="preserve">
<value>Enable Local Site Building</value>
</data>
<data name="CWStaticSiteFeaturesDrafts" xml:space="preserve">
<value>Enable Remote Drafts</value>
</data>
<data name="CWStaticSiteFeaturesImages" xml:space="preserve">
<value>Enable Image Upload</value>
</data>
<data name="CWStaticSiteFeaturesPages" xml:space="preserve">
<value>Enable Pages</value>
</data>
<data name="CWStaticSiteFeaturesSubtitle" xml:space="preserve">
<value>Use the checkboxes below to define which features your static site supports. You can configure Open Live Writer for these features on the following pages.</value>
</data>
<data name="CWStaticSiteFeaturesTitle" xml:space="preserve">
<value>Define static site features</value>
</data>
<data name="CWStaticSiteInitialSubtitle" xml:space="preserve">
<value>{0} will attempt to automatically detect your static site configuration based on files present in your site project folder. Please select the project folder of your static site (eg. Git repository)</value>
<comment>{0} - Product name</comment>
</data>
<data name="CWStaticSiteInitialSubtitleAlreadyDetected" xml:space="preserve">
<value>{0} has already attempted configuration detection on your site. You can change the path to your site here if you wish. Otherwise, you can re-run configuration detection from Settings.</value>
<comment>{0} - Product name</comment>
</data>
<data name="CWStaticSiteInitialTitle" xml:space="preserve">
<value>Provide static site configuration</value>
</data>
<data name="CWStaticSiteLocalSiteFolderPicker" xml:space="preserve">
<value>Please select the project folder of your static site (eg. Git repository)</value>
</data>
<data name="CWStaticSiteLocalSitePath" xml:space="preserve">
<value>Path to local static site:</value>
</data>
<data name="CWStaticSitePathsDraftsPath" xml:space="preserve">
<value>Drafts path: (relative, required if drafts enabled)</value>
</data>
<data name="CWStaticSitePathsImagesPath" xml:space="preserve">
<value>Images path: (relative, required if images enabled)</value>
</data>
<data name="CWStaticSitePathsOutputPath" xml:space="preserve">
<value>Build output path: (relative, required if building enabled)</value>
</data>
<data name="CWStaticSitePathsPagesInRoot" xml:space="preserve">
<value>Pages stored in site root</value>
</data>
<data name="CWStaticSitePathsPagesPath" xml:space="preserve">
<value>Pages path: (relative, required if pages enabled)</value>
</data>
<data name="CWStaticSitePathsPostsPath" xml:space="preserve">
<value>Posts path: (relative)</value>
</data>
<data name="CWStaticSitePathsSiteUrl" xml:space="preserve">
<value>Public site URL:</value>
</data>
<data name="CWStaticSitePathsTitle" xml:space="preserve">
<value>Provide site URL and paths</value>
</data>
<data name="CWTitle" xml:space="preserve">
<value>Add Blog Wizard</value>
@ -1526,10 +1613,6 @@
<value>Martini glass</value>
<comment>Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.</comment>
</data>
<data name="EmoticonMessenger" xml:space="preserve">
<value>Messenger</value>
<comment>Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.</comment>
</data>
<data name="EmoticonMobilePhone" xml:space="preserve">
<value>Mobile phone</value>
<comment>Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.</comment>
@ -1754,10 +1837,6 @@
<value>Work</value>
<comment>Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.</comment>
</data>
<data name="EmoticonXbox" xml:space="preserve">
<value>Xbox</value>
<comment>Alternate text for the emoticon. The emoticon is from Messenger so use Messenger's localization to avoid duplicate work.</comment>
</data>
<data name="Enabled" xml:space="preserve">
<value>Enabled</value>
</data>
@ -2723,10 +2802,10 @@ Unimplemented Method: {2}</value>
<value>Paragraphs</value>
</data>
<data name="PassportLoginNoUserPass" xml:space="preserve">
<value>Please enter a Microsoft Account ID and password to continue.</value>
<value>Please enter a Microsoft Account and password to continue.</value>
</data>
<data name="PassportLoginNoUserPassTitle" xml:space="preserve">
<value>Microsoft Account ID and Password Required</value>
<value>Microsoft Account and Password Required</value>
</data>
<data name="PassportLoginSignIn" xml:space="preserve">
<value>Sign in</value>
@ -2777,6 +2856,12 @@ Unimplemented Method: {2}</value>
<data name="PasteSpecialThinnedLabel" xml:space="preserve">
<value>&amp;Thinned HTML</value>
</data>
<data name="Percent" xml:space="preserve">
<value>percent</value>
</data>
<data name="PhotoPreview" xml:space="preserve">
<value>Inline Photo Previewer</value>
</data>
<data name="PingPrefName" xml:space="preserve">
<value>Ping Servers</value>
</data>
@ -3261,6 +3346,9 @@ Unimplemented Method: {2}</value>
<data name="PostEditorPrefAuto" xml:space="preserve">
<value>Save A&amp;utoRecover information periodically</value>
</data>
<data name="PostEditorPrefBrowseFolder" xml:space="preserve">
<value>Browse</value>
</data>
<data name="PostEditorPrefClose" xml:space="preserve">
<value>Close &amp;window after publishing</value>
</data>
@ -3270,15 +3358,15 @@ Unimplemented Method: {2}</value>
<data name="PostEditorPrefGeneral" xml:space="preserve">
<value>General options</value>
</data>
<data name="PostEditorPrefPostLocation" xml:space="preserve">
<value>Local drafts and recent posts folder</value>
</data>
<data name="PostEditorPrefName" xml:space="preserve">
<value>Preferences</value>
</data>
<data name="PostEditorPrefNew" xml:space="preserve">
<value>Open a new window for &amp;each post</value>
</data>
<data name="PostEditorPrefPostLocation" xml:space="preserve">
<value>Local drafts and recent posts folder</value>
</data>
<data name="PostEditorPrefPostWindows" xml:space="preserve">
<value>Post window</value>
</data>
@ -3305,9 +3393,7 @@ Unimplemented Method: {2}</value>
</data>
<data name="PostEditorPrefView" xml:space="preserve">
<value>&amp;View blog after publishing</value>
</data>
<data name="PostEditorPrefBrowseFolder" xml:space="preserve">
<value>Browse</value>
<comment>Modified on 2/19/2016 by @kathweaver to resolve Issue #377</comment>
</data>
<data name="PostEditorStorageExceptionMessage" xml:space="preserve">
<value>Unexpected error occurred while accessing local post ({0})
@ -3465,6 +3551,10 @@ Unimplemented Method: {2}</value>
<data name="ProgressDownloadingWeblogEditingStyle" xml:space="preserve">
<value>Downloading blog editing theme...</value>
</data>
<data name="ProgressDownloadingWeblogEditingStyleDeep" xml:space="preserve">
<value>Post contents not present on homepage, checking post...</value>
<comment>Used when downloading template from a post page is required</comment>
</data>
<data name="ProgressFinalizingEditingTemplateConfig" xml:space="preserve">
<value>Finalizing editing template configuration...</value>
</data>
@ -3788,9 +3878,6 @@ Do you still want to set this web address as your custom pin?</value>
<data name="SpellingPrefName" xml:space="preserve">
<value>Spelling</value>
</data>
<data name="SpellingPrefNum" xml:space="preserve">
<value>Ignore words with &amp;numbers</value>
</data>
<data name="SpellingPrefOptions" xml:space="preserve">
<value>General options</value>
</data>
@ -3800,9 +3887,6 @@ Do you still want to set this web address as your custom pin?</value>
<data name="SpellingPrefReal" xml:space="preserve">
<value>Use &amp;real-time spell checking (squiggles)</value>
</data>
<data name="SpellingPrefUpper" xml:space="preserve">
<value>Ignore words in &amp;UPPERCASE</value>
</data>
<data name="SpellNoSuggest" xml:space="preserve">
<value>(No suggestions)</value>
<comment>Used with punctuation to indicate that no alternate spellings were returned</comment>
@ -3820,7 +3904,7 @@ Do you still want to set this web address as your custom pin?</value>
<value>Check Spelling</value>
</data>
<data name="SpinnerPixelFormatString" xml:space="preserve">
<value> px</value>
<value>px</value>
<comment>Unit of measurement (pixels) that is appended to the width and height of an image (e.g. 400 px) in the ribbon spinners</comment>
</data>
<data name="SpinnerPixelRepresentativeString" xml:space="preserve">
@ -3833,6 +3917,254 @@ Do you still want to set this web address as your custom pin?</value>
<data name="SplitterMore" xml:space="preserve">
<value>More...</value>
</data>
<data name="SSGBuildErrorText" xml:space="preserve">
<value>{0} has failed to build your site. Please ensure your site builds manually, check your build command and try again.
Build command exit code: {1}
Command STDOUT:
{2}
Command STDERR:
{3}
</value>
<comment>{0} - Product name, {1} - Build command exit code</comment>
</data>
<data name="SSGBuildErrorTitle" xml:space="preserve">
<value>Static site build failed</value>
</data>
<data name="SSGConfigAuthoringDraftsPath" xml:space="preserve">
<value>Drafts Path: (relative)</value>
</data>
<data name="SSGConfigAuthoringEnableDrafts" xml:space="preserve">
<value>Enable &amp;Drafts</value>
</data>
<data name="SSGConfigAuthoringEnableImages" xml:space="preserve">
<value>Enable &amp;Images</value>
</data>
<data name="SSGConfigAuthoringEnablePages" xml:space="preserve">
<value>Enable P&amp;ages</value>
</data>
<data name="SSGConfigAuthoringImagesGroup" xml:space="preserve">
<value>Images</value>
</data>
<data name="SSGConfigAuthoringImagesPath" xml:space="preserve">
<value>Images Path: (relative)</value>
</data>
<data name="SSGConfigAuthoringPagesGroup" xml:space="preserve">
<value>Pages</value>
</data>
<data name="SSGConfigAuthoringPagesInRoot" xml:space="preserve">
<value>Pages Stored In Project Root</value>
</data>
<data name="SSGConfigAuthoringPagesPath" xml:space="preserve">
<value>Pages Path: (relative)</value>
</data>
<data name="SSGConfigAuthoringPostsDraftsGroup" xml:space="preserve">
<value>Posts and Drafts</value>
</data>
<data name="SSGConfigAuthoringPostsPath" xml:space="preserve">
<value>&amp;Posts Path: (relative)</value>
</data>
<data name="SSGConfigAuthoringTitle" xml:space="preserve">
<value>Authoring</value>
</data>
<data name="SSGConfigBuildPublishBuildCommand" xml:space="preserve">
<value>Build Command:</value>
</data>
<data name="SSGConfigBuildPublishBuildingGroup" xml:space="preserve">
<value>Building</value>
</data>
<data name="SSGConfigBuildPublishCmdTimeout" xml:space="preserve">
<value>&amp;Command Timeout (ms):</value>
</data>
<data name="SSGConfigBuildPublishEnableBuilding" xml:space="preserve">
<value>Enable &amp;Building</value>
</data>
<data name="SSGConfigBuildPublishEnableCmdTimeout" xml:space="preserve">
<value>&amp;Enable Command Timeout</value>
</data>
<data name="SSGConfigBuildPublishGeneralGroup" xml:space="preserve">
<value>General</value>
</data>
<data name="SSGConfigBuildPublishOutputPath" xml:space="preserve">
<value>Site Output Path: (relative)</value>
</data>
<data name="SSGConfigBuildPublishPublishCommand" xml:space="preserve">
<value>&amp;Publish Command:</value>
</data>
<data name="SSGConfigBuildPublishPublishingGroup" xml:space="preserve">
<value>Publishing</value>
</data>
<data name="SSGConfigBuildPublishShowCmdWindows" xml:space="preserve">
<value>&amp;Show Command Windows</value>
</data>
<data name="SSGConfigBuildPublishTitle" xml:space="preserve">
<value>Building and Publishing</value>
</data>
<data name="SSGConfigFrontMatterKeyCol" xml:space="preserve">
<value>Front Matter Key</value>
</data>
<data name="SSGConfigFrontMatterPropertyCol" xml:space="preserve">
<value>Property</value>
</data>
<data name="SSGConfigFrontMatterReset" xml:space="preserve">
<value>&amp;Reset to Defaults</value>
</data>
<data name="SSGConfigFrontMatterSubtitle" xml:space="preserve">
<value>Below you can adjust the post front matter keys used to match your static site generator.</value>
</data>
<data name="SSGConfigFrontMatterTitle" xml:space="preserve">
<value>Front Matter</value>
</data>
<data name="SSGConfigGeneralDetectButton" xml:space="preserve">
<value>Run Auto-&amp;Detect</value>
</data>
<data name="SSGConfigGeneralDetectLabel" xml:space="preserve">
<value>Open Live Writer can also reattempt to detect relevant configuration options for your static site. This may not result in a complete configuration, so please use the fields on the following pages to ensure all settings are set correctly.</value>
</data>
<data name="SSGConfigGeneralLocalSitePath" xml:space="preserve">
<value>&amp;Local Site Path:</value>
</data>
<data name="SSGConfigGeneralOptionsGroup" xml:space="preserve">
<value>Options</value>
</data>
<data name="SSGConfigGeneralSetupGroup" xml:space="preserve">
<value>Setup</value>
</data>
<data name="SSGConfigGeneralSiteTitle" xml:space="preserve">
<value>Site &amp;Title:</value>
</data>
<data name="SSGConfigGeneralSiteUrl" xml:space="preserve">
<value>Site &amp;URL:</value>
</data>
<data name="SSGConfigGeneralTitle" xml:space="preserve">
<value>General</value>
</data>
<data name="SSGConfigGeneralWizardButton" xml:space="preserve">
<value>Run Account &amp;Wizard</value>
</data>
<data name="SSGConfigGeneralWizardLabel" xml:space="preserve">
<value>You can choose to run the Account Wizard again if you wish to be guided through the core static site configuration options interactively.</value>
</data>
<data name="SSGConfigTitle" xml:space="preserve">
<value>Static Site Configuration for '{0}'</value>
<comment>{0} - title of blog</comment>
</data>
<data name="SSGErrorBuildCommandEmptyText" xml:space="preserve">
<value>A build command is required when local site building is enabled.</value>
</data>
<data name="SSGErrorBuildCommandEmptyTitle" xml:space="preserve">
<value>Build command empty</value>
</data>
<data name="SSGErrorCommandTimeoutText" xml:space="preserve">
<value>Blog command timed out. Please check your commands, or lengthen the command timeout.</value>
</data>
<data name="SSGErrorCommandTimeoutTitle" xml:space="preserve">
<value>Command execution timeout</value>
</data>
<data name="SSGErrorItemLoadTextFM" xml:space="preserve">
<value>Could not read item front matter.</value>
</data>
<data name="SSGErrorItemLoadTextId" xml:space="preserve">
<value>Item does not have an ID.</value>
</data>
<data name="SSGErrorItemLoadTitle" xml:space="preserve">
<value>Item load error</value>
</data>
<data name="SSGErrorPageDoesNotExistText" xml:space="preserve">
<value>Could not find page with specified ID.</value>
</data>
<data name="SSGErrorPageDoesNotExistTitle" xml:space="preserve">
<value>Page does not exist</value>
</data>
<data name="SSGErrorPathDraftsEmpty" xml:space="preserve">
<value>Drafts path is empty.</value>
</data>
<data name="SSGErrorPathDraftsNotFound" xml:space="preserve">
<value>Drafts path '{0}' does not exist.</value>
<comment>{0} - attempted drafts path</comment>
</data>
<data name="SSGErrorPathFolderNotFound" xml:space="preserve">
<value>Folder not found</value>
</data>
<data name="SSGErrorPathImagesEmpty" xml:space="preserve">
<value>Images path is empty.</value>
</data>
<data name="SSGErrorPathImagesNotFound" xml:space="preserve">
<value>Images path '{0}' does not exist.</value>
<comment>{0} - attempted images path</comment>
</data>
<data name="SSGErrorPathLocalSitePathNotFound" xml:space="preserve">
<value>Local site path '{0}' does not exist.</value>
<comment>{0} - attempted local site path</comment>
</data>
<data name="SSGErrorPathOutputEmpty" xml:space="preserve">
<value>Output path is empty.</value>
</data>
<data name="SSGErrorPathOutputNotFound" xml:space="preserve">
<value>Output path '{0}' does not exist.</value>
<comment>{0} - attempted output path</comment>
</data>
<data name="SSGErrorPathPagesEmpty" xml:space="preserve">
<value>Pages path is empty.</value>
</data>
<data name="SSGErrorPathPagesNotFound" xml:space="preserve">
<value>Pages path '{0}' does not exist.</value>
<comment>{0} - attempted pages path</comment>
</data>
<data name="SSGErrorPathPostsEmpty" xml:space="preserve">
<value>Posts path is empty.</value>
</data>
<data name="SSGErrorPathPostsNotFound" xml:space="preserve">
<value>Posts path '{0}' does not exist.</value>
<comment>{0} - attempted posts path</comment>
</data>
<data name="SSGErrorPostDoesNotExistText" xml:space="preserve">
<value>Could not find post with specified ID.</value>
</data>
<data name="SSGErrorPostDoesNotExistTitle" xml:space="preserve">
<value>Post does not exist</value>
</data>
<data name="SSGErrorPublishCommandEmptyText" xml:space="preserve">
<value>A publish command is required.</value>
</data>
<data name="SSGErrorPublishCommandEmptyTitle" xml:space="preserve">
<value>Publish command empty</value>
</data>
<data name="SSGFrontMatterDate" xml:space="preserve">
<value>Date</value>
</data>
<data name="SSGFrontMatterId" xml:space="preserve">
<value>ID</value>
</data>
<data name="SSGFrontMatterLayout" xml:space="preserve">
<value>Layout</value>
</data>
<data name="SSGFrontMatterParentId" xml:space="preserve">
<value>Parent ID</value>
</data>
<data name="SSGFrontMatterPermalink" xml:space="preserve">
<value>Permalink</value>
</data>
<data name="SSGFrontMatterTags" xml:space="preserve">
<value>Tags</value>
</data>
<data name="SSGFrontMatterTitle" xml:space="preserve">
<value>Title</value>
</data>
<data name="SSGPublishErrorText" xml:space="preserve">
<value>{0} has failed to publish your site. Please check your site publish command.
Publish command exit code: {1}
Command STDOUT:
{2}
Command STDERR:
{3}
</value>
<comment>{0} - Product name, {1} - Publish command exit code</comment>
</data>
<data name="SSGPublishErrorTitle" xml:space="preserve">
<value>Static site publish failed</value>
</data>
<data name="Statistics" xml:space="preserve">
<value>Statistics</value>
<comment>Header of the table that shows word and character count</comment>
@ -4091,6 +4423,9 @@ Do you still want to set this web address as your custom pin?</value>
<data name="UnexpectedErrorPluginTitle" xml:space="preserve">
<value>Plug-in Error Occurred</value>
</data>
<data name="UnexpectedErrorSendError" xml:space="preserve">
<value>&amp;Send Error</value>
</data>
<data name="UnexpectedErrorTitle" xml:space="preserve">
<value>Unexpected Error</value>
</data>
@ -4380,9 +4715,6 @@ This might take a while.</value>
<data name="WidthLabel" xml:space="preserve">
<value>&amp;Width:</value>
</data>
<data name="PhotoPreview" xml:space="preserve">
<value>Inline Photo Previewer</value>
</data>
<data name="WindowTitleFormat" xml:space="preserve">
<value>{0} - {1}</value>
<comment>{0} - post title, {1} - "Open Live Writer"</comment>
@ -4407,6 +4739,9 @@ This might take a while.</value>
<data name="WizardBlogTypeSharePoint" xml:space="preserve">
<value>&amp;SharePoint</value>
</data>
<data name="WizardBlogTypeStaticSite" xml:space="preserve">
<value>Static Site G&amp;enerator</value>
</data>
<data name="WizardBlogTypeWelcome" xml:space="preserve">
<value>Many popular blog services work with {0}.</value>
<comment>{0} - Long product name, i.e. "Open Live Writer"</comment>
@ -4483,10 +4818,4 @@ This might take a while.</value>
<data name="YouTubeVideoError" xml:space="preserve">
<value>There was an unexpected error while uploading the video.</value>
</data>
<data name="UnexpectedErrorSendError" xml:space="preserve">
<value>&amp;Send Error</value>
</data>
<data name="Percent" xml:space="preserve">
<value>percent</value>
</data>
</root>

View File

@ -122,13 +122,21 @@ namespace OpenLiveWriter.PostEditor.Configuration.Settings
TemporaryBlogSettingsModified = true;
}
private delegate bool EditTemporarySettingsDelegate(IWin32Window window, TemporaryBlogSettings settings);
private void buttonEditConfiguration_Click(object sender, EventArgs e)
{
// make a copy of the temporary settings to edit
TemporaryBlogSettings blogSettings = TemporaryBlogSettings.Clone() as TemporaryBlogSettings;
// edit account info
if (WeblogConfigurationWizardController.EditTemporarySettings(FindForm(), blogSettings))
// Edit account info
// For static sites, the advanced configuration panel will be displayed
// Otherwise, display the wizard
bool settingsModified = blogSettings.IsStaticSiteBlog
? StaticSiteAdvanced.StaticSitePreferencesController.EditTemporarySettings(FindForm(), blogSettings)
: WeblogConfigurationWizardController.EditTemporarySettings(FindForm(), blogSettings);
if (settingsModified)
{
// go ahead and save the settings back
TemporaryBlogSettings.CopyFrom(blogSettings);

View File

@ -0,0 +1,411 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.Controls;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.PostEditor;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.PostEditor.Configuration.Wizard;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
namespace OpenLiveWriter.PostEditor.Configuration.StaticSiteAdvanced
{
/// <summary>
/// Summary description for AccountPanel.
/// </summary>
public class AuthoringPanel : StaticSitePreferencesPanel
{
private Label labelPostsPath;
private TextBox textBoxPostsPath;
private GroupBox groupBoxPosts;
private CheckBox checkBoxDraftsEnabled;
private TextBox textBoxDraftsPath;
private Label labelDraftsPath;
private GroupBox groupBoxPages;
private CheckBox checkBoxPagesStoredInRoot;
private CheckBox checkBoxPagesEnabled;
private TextBox textBoxPagesPath;
private Label labelPagesPath;
private GroupBox groupBoxImages;
private CheckBox checkBoxImagesEnabled;
private TextBox textBoxImagesPath;
private Label labelImagesPath;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public string PostsPath
{
get => textBoxPostsPath.Text;
set => textBoxPostsPath.Text = value;
}
public bool DraftsEnabled
{
get => checkBoxDraftsEnabled.Checked;
set => checkBoxDraftsEnabled.Checked = value;
}
public string DraftsPath
{
get => textBoxDraftsPath.Text;
set => textBoxDraftsPath.Text = value;
}
public bool PagesEnabled
{
get => checkBoxPagesEnabled.Checked;
set => checkBoxPagesEnabled.Checked = value;
}
public string PagesPath
{
get => textBoxPagesPath.Text;
set => textBoxPagesPath.Text = value;
}
public bool PagesStoredInRoot
{
get => checkBoxPagesStoredInRoot.Checked;
set => checkBoxPagesStoredInRoot.Checked = value;
}
public bool ImagesEnabled
{
get => checkBoxImagesEnabled.Checked;
set => checkBoxImagesEnabled.Checked = value;
}
public string ImagesPath
{
get => textBoxImagesPath.Text;
set => textBoxImagesPath.Text = value;
}
public AuthoringPanel() : base()
{
InitializeComponent();
LocalizeStrings();
}
public AuthoringPanel(StaticSitePreferencesController controller)
: base(controller)
{
InitializeComponent();
LocalizeStrings();
}
private void LocalizeStrings()
{
PanelName = Res.Get(StringId.SSGConfigAuthoringTitle);
groupBoxPosts.Text = Res.Get(StringId.SSGConfigAuthoringPostsDraftsGroup);
labelPostsPath.Text = Res.Get(StringId.SSGConfigAuthoringPostsPath);
checkBoxDraftsEnabled.Text = Res.Get(StringId.SSGConfigAuthoringEnableDrafts);
labelDraftsPath.Text = Res.Get(StringId.SSGConfigAuthoringDraftsPath);
groupBoxPages.Text = Res.Get(StringId.SSGConfigAuthoringPagesGroup);
checkBoxPagesEnabled.Text = Res.Get(StringId.SSGConfigAuthoringEnablePages);
labelPagesPath.Text = Res.Get(StringId.SSGConfigAuthoringPagesPath);
checkBoxPagesStoredInRoot.Text = Res.Get(StringId.SSGConfigAuthoringPagesInRoot);
groupBoxImages.Text = Res.Get(StringId.SSGConfigAuthoringImagesGroup);
checkBoxImagesEnabled.Text = Res.Get(StringId.SSGConfigAuthoringEnableImages);
labelImagesPath.Text = Res.Get(StringId.SSGConfigAuthoringImagesPath);
}
public override void LoadConfig()
{
PostsPath = _controller.Config.PostsPath;
DraftsEnabled = _controller.Config.DraftsEnabled;
DraftsPath = _controller.Config.DraftsPath;
PagesEnabled = _controller.Config.PagesEnabled;
PagesPath = _controller.Config.PagesPath;
PagesStoredInRoot = _controller.Config.PagesPath == ".";
ImagesEnabled = _controller.Config.ImagesEnabled;
ImagesPath = _controller.Config.ImagesPath;
}
public override void ValidateConfig()
=> _controller.Config.Validator
.ValidatePostsPath()
.ValidateDraftsPath()
.ValidatePagesPath()
.ValidateImagesPath();
public override void Save()
{
_controller.Config.PostsPath = PostsPath;
_controller.Config.DraftsEnabled = DraftsEnabled;
_controller.Config.DraftsPath = DraftsPath;
_controller.Config.PagesEnabled = PagesEnabled;
_controller.Config.PagesPath = PagesStoredInRoot ? "." : PagesPath;
_controller.Config.ImagesEnabled = ImagesEnabled;
_controller.Config.ImagesPath = ImagesPath;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
RecomputeEnabledStates();
LayoutHelper.NaturalizeHeight(labelPostsPath, textBoxPostsPath, checkBoxDraftsEnabled, labelDraftsPath, textBoxDraftsPath);
LayoutHelper.NaturalizeHeight(checkBoxPagesEnabled, labelPagesPath, textBoxPagesPath, checkBoxPagesStoredInRoot);
LayoutHelper.NaturalizeHeight(checkBoxImagesEnabled, labelImagesPath, textBoxImagesPath);
}
/// <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 Component 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()
{
this.labelPostsPath = new System.Windows.Forms.Label();
this.textBoxPostsPath = new System.Windows.Forms.TextBox();
this.groupBoxPosts = new System.Windows.Forms.GroupBox();
this.checkBoxDraftsEnabled = new System.Windows.Forms.CheckBox();
this.textBoxDraftsPath = new System.Windows.Forms.TextBox();
this.labelDraftsPath = new System.Windows.Forms.Label();
this.groupBoxPages = new System.Windows.Forms.GroupBox();
this.checkBoxPagesStoredInRoot = new System.Windows.Forms.CheckBox();
this.checkBoxPagesEnabled = new System.Windows.Forms.CheckBox();
this.textBoxPagesPath = new System.Windows.Forms.TextBox();
this.labelPagesPath = new System.Windows.Forms.Label();
this.groupBoxImages = new System.Windows.Forms.GroupBox();
this.checkBoxImagesEnabled = new System.Windows.Forms.CheckBox();
this.textBoxImagesPath = new System.Windows.Forms.TextBox();
this.labelImagesPath = new System.Windows.Forms.Label();
this.groupBoxPosts.SuspendLayout();
this.groupBoxPages.SuspendLayout();
this.groupBoxImages.SuspendLayout();
this.SuspendLayout();
//
// labelPostsPath
//
this.labelPostsPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelPostsPath.Location = new System.Drawing.Point(16, 19);
this.labelPostsPath.Name = "labelPostsPath";
this.labelPostsPath.Size = new System.Drawing.Size(144, 16);
this.labelPostsPath.TabIndex = 0;
this.labelPostsPath.Text = "Posts Path:";
//
// textBoxPostsPath
//
this.textBoxPostsPath.Location = new System.Drawing.Point(16, 38);
this.textBoxPostsPath.Name = "textBoxPostsPath";
this.textBoxPostsPath.Size = new System.Drawing.Size(316, 23);
this.textBoxPostsPath.TabIndex = 1;
//
// groupBoxPosts
//
this.groupBoxPosts.Controls.Add(this.checkBoxDraftsEnabled);
this.groupBoxPosts.Controls.Add(this.textBoxPostsPath);
this.groupBoxPosts.Controls.Add(this.textBoxDraftsPath);
this.groupBoxPosts.Controls.Add(this.labelPostsPath);
this.groupBoxPosts.Controls.Add(this.labelDraftsPath);
this.groupBoxPosts.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.groupBoxPosts.Location = new System.Drawing.Point(8, 32);
this.groupBoxPosts.Name = "groupBoxPosts";
this.groupBoxPosts.Size = new System.Drawing.Size(345, 144);
this.groupBoxPosts.TabIndex = 1;
this.groupBoxPosts.TabStop = false;
this.groupBoxPosts.Text = "Posts and Drafts";
//
// checkBoxDraftsEnabled
//
this.checkBoxDraftsEnabled.AutoSize = true;
this.checkBoxDraftsEnabled.Location = new System.Drawing.Point(16, 70);
this.checkBoxDraftsEnabled.Margin = new System.Windows.Forms.Padding(3, 0, 3, 0);
this.checkBoxDraftsEnabled.Name = "checkBoxDraftsEnabled";
this.checkBoxDraftsEnabled.Size = new System.Drawing.Size(95, 19);
this.checkBoxDraftsEnabled.TabIndex = 2;
this.checkBoxDraftsEnabled.Text = "Enable Drafts";
this.checkBoxDraftsEnabled.UseVisualStyleBackColor = true;
this.checkBoxDraftsEnabled.CheckedChanged += new System.EventHandler(this.CheckBoxDraftsEnabled_CheckedChanged);
//
// textBoxDraftsPath
//
this.textBoxDraftsPath.Location = new System.Drawing.Point(16, 108);
this.textBoxDraftsPath.Name = "textBoxDraftsPath";
this.textBoxDraftsPath.Size = new System.Drawing.Size(316, 23);
this.textBoxDraftsPath.TabIndex = 4;
//
// labelDraftsPath
//
this.labelDraftsPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelDraftsPath.Location = new System.Drawing.Point(16, 89);
this.labelDraftsPath.Name = "labelDraftsPath";
this.labelDraftsPath.Size = new System.Drawing.Size(144, 16);
this.labelDraftsPath.TabIndex = 3;
this.labelDraftsPath.Text = "Drafts Path:";
//
// groupBoxPages
//
this.groupBoxPages.Controls.Add(this.checkBoxPagesStoredInRoot);
this.groupBoxPages.Controls.Add(this.checkBoxPagesEnabled);
this.groupBoxPages.Controls.Add(this.textBoxPagesPath);
this.groupBoxPages.Controls.Add(this.labelPagesPath);
this.groupBoxPages.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.groupBoxPages.Location = new System.Drawing.Point(8, 182);
this.groupBoxPages.Name = "groupBoxPages";
this.groupBoxPages.Size = new System.Drawing.Size(345, 120);
this.groupBoxPages.TabIndex = 2;
this.groupBoxPages.TabStop = false;
this.groupBoxPages.Text = "Pages";
//
// checkBoxPagesStoredInRoot
//
this.checkBoxPagesStoredInRoot.AutoSize = true;
this.checkBoxPagesStoredInRoot.Location = new System.Drawing.Point(16, 89);
this.checkBoxPagesStoredInRoot.Margin = new System.Windows.Forms.Padding(3, 0, 3, 3);
this.checkBoxPagesStoredInRoot.Name = "checkBoxPagesStoredInRoot";
this.checkBoxPagesStoredInRoot.Size = new System.Drawing.Size(175, 19);
this.checkBoxPagesStoredInRoot.TabIndex = 3;
this.checkBoxPagesStoredInRoot.Text = "Pages Stored In Project Root";
this.checkBoxPagesStoredInRoot.UseVisualStyleBackColor = true;
this.checkBoxPagesStoredInRoot.CheckedChanged += new System.EventHandler(this.CheckBoxPagesStoredInRoot_CheckedChanged);
//
// checkBoxPagesEnabled
//
this.checkBoxPagesEnabled.AutoSize = true;
this.checkBoxPagesEnabled.Location = new System.Drawing.Point(16, 22);
this.checkBoxPagesEnabled.Margin = new System.Windows.Forms.Padding(3, 3, 3, 0);
this.checkBoxPagesEnabled.Name = "checkBoxPagesEnabled";
this.checkBoxPagesEnabled.Size = new System.Drawing.Size(95, 19);
this.checkBoxPagesEnabled.TabIndex = 0;
this.checkBoxPagesEnabled.Text = "Enable Pages";
this.checkBoxPagesEnabled.UseVisualStyleBackColor = true;
this.checkBoxPagesEnabled.CheckedChanged += new System.EventHandler(this.CheckBoxPagesEnabled_CheckedChanged);
//
// textBoxPagesPath
//
this.textBoxPagesPath.Location = new System.Drawing.Point(16, 63);
this.textBoxPagesPath.Name = "textBoxPagesPath";
this.textBoxPagesPath.Size = new System.Drawing.Size(316, 23);
this.textBoxPagesPath.TabIndex = 2;
//
// labelPagesPath
//
this.labelPagesPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelPagesPath.Location = new System.Drawing.Point(16, 44);
this.labelPagesPath.Name = "labelPagesPath";
this.labelPagesPath.Size = new System.Drawing.Size(144, 16);
this.labelPagesPath.TabIndex = 1;
this.labelPagesPath.Text = "Pages Path:";
//
// groupBoxImages
//
this.groupBoxImages.Controls.Add(this.checkBoxImagesEnabled);
this.groupBoxImages.Controls.Add(this.textBoxImagesPath);
this.groupBoxImages.Controls.Add(this.labelImagesPath);
this.groupBoxImages.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.groupBoxImages.Location = new System.Drawing.Point(8, 308);
this.groupBoxImages.Name = "groupBoxImages";
this.groupBoxImages.Size = new System.Drawing.Size(345, 101);
this.groupBoxImages.TabIndex = 3;
this.groupBoxImages.TabStop = false;
this.groupBoxImages.Text = "Images";
//
// checkBoxImagesEnabled
//
this.checkBoxImagesEnabled.AutoSize = true;
this.checkBoxImagesEnabled.Location = new System.Drawing.Point(16, 22);
this.checkBoxImagesEnabled.Name = "checkBoxImagesEnabled";
this.checkBoxImagesEnabled.Size = new System.Drawing.Size(102, 19);
this.checkBoxImagesEnabled.TabIndex = 0;
this.checkBoxImagesEnabled.Text = "Enable Images";
this.checkBoxImagesEnabled.UseVisualStyleBackColor = true;
this.checkBoxImagesEnabled.CheckedChanged += new System.EventHandler(this.CheckBoxImagesEnabled_CheckedChanged);
//
// textBoxImagesPath
//
this.textBoxImagesPath.Location = new System.Drawing.Point(16, 63);
this.textBoxImagesPath.Name = "textBoxImagesPath";
this.textBoxImagesPath.Size = new System.Drawing.Size(316, 23);
this.textBoxImagesPath.TabIndex = 2;
//
// labelImagesPath
//
this.labelImagesPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelImagesPath.Location = new System.Drawing.Point(16, 44);
this.labelImagesPath.Name = "labelImagesPath";
this.labelImagesPath.Size = new System.Drawing.Size(144, 16);
this.labelImagesPath.TabIndex = 1;
this.labelImagesPath.Text = "Images Path:";
//
// AuthoringPanel
//
this.AccessibleName = "Authoring";
this.Controls.Add(this.groupBoxImages);
this.Controls.Add(this.groupBoxPages);
this.Controls.Add(this.groupBoxPosts);
this.Name = "AuthoringPanel";
this.PanelName = "Authoring";
this.Size = new System.Drawing.Size(370, 425);
this.Controls.SetChildIndex(this.groupBoxPosts, 0);
this.Controls.SetChildIndex(this.groupBoxPages, 0);
this.Controls.SetChildIndex(this.groupBoxImages, 0);
this.groupBoxPosts.ResumeLayout(false);
this.groupBoxPosts.PerformLayout();
this.groupBoxPages.ResumeLayout(false);
this.groupBoxPages.PerformLayout();
this.groupBoxImages.ResumeLayout(false);
this.groupBoxImages.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private void CheckBoxDraftsEnabled_CheckedChanged(object sender, EventArgs e)
=> RecomputeEnabledStates();
private void CheckBoxPagesEnabled_CheckedChanged(object sender, EventArgs e)
=> RecomputeEnabledStates();
private void CheckBoxPagesStoredInRoot_CheckedChanged(object sender, EventArgs e)
{
RecomputeEnabledStates();
if (checkBoxPagesStoredInRoot.Checked) textBoxPagesPath.Text = ".";
}
private void CheckBoxImagesEnabled_CheckedChanged(object sender, EventArgs e)
=> RecomputeEnabledStates();
private void RecomputeEnabledStates()
{
textBoxDraftsPath.Enabled = checkBoxDraftsEnabled.Checked;
textBoxPagesPath.Enabled = checkBoxPagesEnabled.Checked && !checkBoxPagesStoredInRoot.Checked;
checkBoxPagesStoredInRoot.Enabled = checkBoxPagesEnabled.Checked;
textBoxImagesPath.Enabled = checkBoxImagesEnabled.Checked;
}
}
}

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -0,0 +1,395 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.Controls;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.PostEditor;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.PostEditor.Configuration.Wizard;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
namespace OpenLiveWriter.PostEditor.Configuration.StaticSiteAdvanced
{
/// <summary>
/// Summary description for BuildPublishPanel.
/// </summary>
public class BuildPublishPanel : StaticSitePreferencesPanel
{
private System.Windows.Forms.GroupBox groupBoxGeneral;
private CheckBox checkBoxShowCommandWindows;
private Label labelCmdTimeout;
private NumericUpDown numericUpDownCmdTimeout;
private CheckBox checkBoxEnableCmdTimeout;
private GroupBox groupBoxBuilding;
private TextBox textBoxBuildCommand;
private Label labelBuildCommand;
private CheckBox checkBoxBuildingEnabled;
private TextBox textBoxOutputPath;
private Label labelOutputPath;
private GroupBox groupBoxPublishing;
private TextBox textBoxPublishCommand;
private Label labelPublishCommand;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public bool ShowCmdWindows
{
get => checkBoxShowCommandWindows.Checked;
set => checkBoxShowCommandWindows.Checked = value;
}
public int CmdTimeoutMs
{
get
{
if (checkBoxEnableCmdTimeout.Checked)
return Convert.ToInt32(numericUpDownCmdTimeout.Value);
return -1; // -1 for disabled
}
set
{
if (value >= 0)
{
checkBoxEnableCmdTimeout.Checked = true;
numericUpDownCmdTimeout.Value = value;
}
else
checkBoxEnableCmdTimeout.Checked = false;
RecomputeEnabledStates();
}
}
public bool BuildingEnabled
{
get => checkBoxBuildingEnabled.Checked;
set => checkBoxBuildingEnabled.Checked = value;
}
public string BuildCommand
{
get => textBoxBuildCommand.Text;
set => textBoxBuildCommand.Text = value;
}
public string OutputPath
{
get => textBoxOutputPath.Text;
set => textBoxOutputPath.Text = value;
}
public string PublishCommand
{
get => textBoxPublishCommand.Text;
set => textBoxPublishCommand.Text = value;
}
public BuildPublishPanel() : base()
{
InitializeComponent();
LocalizeStrings();
numericUpDownCmdTimeout.Maximum = int.MaxValue;
}
public BuildPublishPanel(StaticSitePreferencesController controller)
: base(controller)
{
InitializeComponent();
LocalizeStrings();
numericUpDownCmdTimeout.Maximum = int.MaxValue;
}
private void LocalizeStrings()
{
groupBoxGeneral.Text = Res.Get(StringId.SSGConfigBuildPublishGeneralGroup);
checkBoxShowCommandWindows.Text = Res.Get(StringId.SSGConfigBuildPublishShowCmdWindows);
labelCmdTimeout.Text = Res.Get(StringId.SSGConfigBuildPublishCmdTimeout);
checkBoxEnableCmdTimeout.Text = Res.Get(StringId.SSGConfigBuildPublishEnableCmdTimeout);
groupBoxBuilding.Text = Res.Get(StringId.SSGConfigBuildPublishBuildingGroup);
labelBuildCommand.Text = Res.Get(StringId.SSGConfigBuildPublishBuildCommand);
checkBoxBuildingEnabled.Text = Res.Get(StringId.SSGConfigBuildPublishEnableBuilding);
labelOutputPath.Text = Res.Get(StringId.SSGConfigBuildPublishOutputPath);
groupBoxPublishing.Text = Res.Get(StringId.SSGConfigBuildPublishPublishingGroup);
labelPublishCommand.Text = Res.Get(StringId.SSGConfigBuildPublishPublishCommand);
}
public override void LoadConfig()
{
ShowCmdWindows = _controller.Config.ShowCmdWindows;
CmdTimeoutMs = _controller.Config.CmdTimeoutMs;
BuildingEnabled = _controller.Config.BuildingEnabled;
BuildCommand = _controller.Config.BuildCommand;
OutputPath = _controller.Config.OutputPath;
PublishCommand = _controller.Config.PublishCommand;
}
public override void ValidateConfig()
=> _controller.Config.Validator
.ValidateBuildCommand()
.ValidateOutputPath()
.ValidatePublishCommand();
public override void Save()
{
_controller.Config.ShowCmdWindows = ShowCmdWindows;
_controller.Config.CmdTimeoutMs = CmdTimeoutMs;
_controller.Config.BuildingEnabled = BuildingEnabled;
_controller.Config.BuildCommand = BuildCommand;
_controller.Config.OutputPath = OutputPath;
_controller.Config.PublishCommand = PublishCommand;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
RecomputeEnabledStates();
}
/// <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 Component 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()
{
this.groupBoxGeneral = new System.Windows.Forms.GroupBox();
this.checkBoxEnableCmdTimeout = new System.Windows.Forms.CheckBox();
this.labelCmdTimeout = new System.Windows.Forms.Label();
this.numericUpDownCmdTimeout = new System.Windows.Forms.NumericUpDown();
this.checkBoxShowCommandWindows = new System.Windows.Forms.CheckBox();
this.groupBoxBuilding = new System.Windows.Forms.GroupBox();
this.textBoxOutputPath = new System.Windows.Forms.TextBox();
this.labelOutputPath = new System.Windows.Forms.Label();
this.textBoxBuildCommand = new System.Windows.Forms.TextBox();
this.labelBuildCommand = new System.Windows.Forms.Label();
this.checkBoxBuildingEnabled = new System.Windows.Forms.CheckBox();
this.groupBoxPublishing = new System.Windows.Forms.GroupBox();
this.textBoxPublishCommand = new System.Windows.Forms.TextBox();
this.labelPublishCommand = new System.Windows.Forms.Label();
this.groupBoxGeneral.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.numericUpDownCmdTimeout)).BeginInit();
this.groupBoxBuilding.SuspendLayout();
this.groupBoxPublishing.SuspendLayout();
this.SuspendLayout();
//
// groupBoxGeneral
//
this.groupBoxGeneral.Controls.Add(this.checkBoxEnableCmdTimeout);
this.groupBoxGeneral.Controls.Add(this.labelCmdTimeout);
this.groupBoxGeneral.Controls.Add(this.numericUpDownCmdTimeout);
this.groupBoxGeneral.Controls.Add(this.checkBoxShowCommandWindows);
this.groupBoxGeneral.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.groupBoxGeneral.Location = new System.Drawing.Point(8, 32);
this.groupBoxGeneral.Name = "groupBoxGeneral";
this.groupBoxGeneral.Size = new System.Drawing.Size(345, 111);
this.groupBoxGeneral.TabIndex = 1;
this.groupBoxGeneral.TabStop = false;
this.groupBoxGeneral.Text = "General";
//
// checkBoxEnableCmdTimeout
//
this.checkBoxEnableCmdTimeout.AutoSize = true;
this.checkBoxEnableCmdTimeout.Location = new System.Drawing.Point(16, 50);
this.checkBoxEnableCmdTimeout.Margin = new System.Windows.Forms.Padding(13, 5, 3, 3);
this.checkBoxEnableCmdTimeout.Name = "checkBoxEnableCmdTimeout";
this.checkBoxEnableCmdTimeout.Size = new System.Drawing.Size(168, 19);
this.checkBoxEnableCmdTimeout.TabIndex = 1;
this.checkBoxEnableCmdTimeout.Text = "Enable Command Timeout";
this.checkBoxEnableCmdTimeout.UseVisualStyleBackColor = true;
this.checkBoxEnableCmdTimeout.CheckedChanged += new System.EventHandler(this.CheckBoxEnableCmdTimeout_CheckedChanged);
//
// labelCmdTimeout
//
this.labelCmdTimeout.AutoSize = true;
this.labelCmdTimeout.Location = new System.Drawing.Point(13, 77);
this.labelCmdTimeout.Name = "labelCmdTimeout";
this.labelCmdTimeout.Size = new System.Drawing.Size(141, 15);
this.labelCmdTimeout.TabIndex = 2;
this.labelCmdTimeout.Text = "Command Timeout (ms):";
//
// numericUpDownCmdTimeout
//
this.numericUpDownCmdTimeout.Location = new System.Drawing.Point(160, 75);
this.numericUpDownCmdTimeout.Maximum = new decimal(new int[] {
0,
0,
0,
0});
this.numericUpDownCmdTimeout.Name = "numericUpDownCmdTimeout";
this.numericUpDownCmdTimeout.Size = new System.Drawing.Size(169, 23);
this.numericUpDownCmdTimeout.TabIndex = 3;
//
// checkBoxShowCommandWindows
//
this.checkBoxShowCommandWindows.AutoSize = true;
this.checkBoxShowCommandWindows.Location = new System.Drawing.Point(16, 23);
this.checkBoxShowCommandWindows.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3);
this.checkBoxShowCommandWindows.Name = "checkBoxShowCommandWindows";
this.checkBoxShowCommandWindows.Size = new System.Drawing.Size(167, 19);
this.checkBoxShowCommandWindows.TabIndex = 0;
this.checkBoxShowCommandWindows.Text = "Show Command Windows";
this.checkBoxShowCommandWindows.UseVisualStyleBackColor = true;
//
// groupBoxBuilding
//
this.groupBoxBuilding.Controls.Add(this.textBoxOutputPath);
this.groupBoxBuilding.Controls.Add(this.labelOutputPath);
this.groupBoxBuilding.Controls.Add(this.textBoxBuildCommand);
this.groupBoxBuilding.Controls.Add(this.labelBuildCommand);
this.groupBoxBuilding.Controls.Add(this.checkBoxBuildingEnabled);
this.groupBoxBuilding.Location = new System.Drawing.Point(8, 149);
this.groupBoxBuilding.Name = "groupBoxBuilding";
this.groupBoxBuilding.Size = new System.Drawing.Size(345, 142);
this.groupBoxBuilding.TabIndex = 2;
this.groupBoxBuilding.TabStop = false;
this.groupBoxBuilding.Text = "Building";
//
// textBoxOutputPath
//
this.textBoxOutputPath.Location = new System.Drawing.Point(16, 106);
this.textBoxOutputPath.Name = "textBoxOutputPath";
this.textBoxOutputPath.Size = new System.Drawing.Size(313, 23);
this.textBoxOutputPath.TabIndex = 8;
//
// labelOutputPath
//
this.labelOutputPath.AutoSize = true;
this.labelOutputPath.Location = new System.Drawing.Point(13, 88);
this.labelOutputPath.Name = "labelOutputPath";
this.labelOutputPath.Size = new System.Drawing.Size(146, 15);
this.labelOutputPath.TabIndex = 7;
this.labelOutputPath.Text = "Site Output Path: (relative)";
//
// textBoxBuildCommand
//
this.textBoxBuildCommand.Location = new System.Drawing.Point(16, 62);
this.textBoxBuildCommand.Margin = new System.Windows.Forms.Padding(13, 3, 13, 3);
this.textBoxBuildCommand.Name = "textBoxBuildCommand";
this.textBoxBuildCommand.Size = new System.Drawing.Size(313, 23);
this.textBoxBuildCommand.TabIndex = 6;
//
// labelBuildCommand
//
this.labelBuildCommand.AutoSize = true;
this.labelBuildCommand.Location = new System.Drawing.Point(13, 44);
this.labelBuildCommand.Margin = new System.Windows.Forms.Padding(13, 0, 3, 0);
this.labelBuildCommand.Name = "labelBuildCommand";
this.labelBuildCommand.Size = new System.Drawing.Size(97, 15);
this.labelBuildCommand.TabIndex = 5;
this.labelBuildCommand.Text = "Build Command:";
//
// checkBoxBuildingEnabled
//
this.checkBoxBuildingEnabled.AutoSize = true;
this.checkBoxBuildingEnabled.Location = new System.Drawing.Point(16, 22);
this.checkBoxBuildingEnabled.Margin = new System.Windows.Forms.Padding(13, 3, 3, 3);
this.checkBoxBuildingEnabled.Name = "checkBoxBuildingEnabled";
this.checkBoxBuildingEnabled.Size = new System.Drawing.Size(108, 19);
this.checkBoxBuildingEnabled.TabIndex = 4;
this.checkBoxBuildingEnabled.Text = "Enable Building";
this.checkBoxBuildingEnabled.UseVisualStyleBackColor = true;
this.checkBoxBuildingEnabled.CheckedChanged += new System.EventHandler(this.CheckBoxBuildingEnabled_CheckedChanged);
//
// groupBoxPublishing
//
this.groupBoxPublishing.Controls.Add(this.textBoxPublishCommand);
this.groupBoxPublishing.Controls.Add(this.labelPublishCommand);
this.groupBoxPublishing.Location = new System.Drawing.Point(8, 298);
this.groupBoxPublishing.Name = "groupBoxPublishing";
this.groupBoxPublishing.Size = new System.Drawing.Size(345, 73);
this.groupBoxPublishing.TabIndex = 3;
this.groupBoxPublishing.TabStop = false;
this.groupBoxPublishing.Text = "Publishing";
//
// textBoxPublishCommand
//
this.textBoxPublishCommand.Location = new System.Drawing.Point(16, 37);
this.textBoxPublishCommand.Margin = new System.Windows.Forms.Padding(13, 3, 13, 3);
this.textBoxPublishCommand.Name = "textBoxPublishCommand";
this.textBoxPublishCommand.Size = new System.Drawing.Size(313, 23);
this.textBoxPublishCommand.TabIndex = 1;
//
// labelPublishCommand
//
this.labelPublishCommand.AutoSize = true;
this.labelPublishCommand.Location = new System.Drawing.Point(13, 19);
this.labelPublishCommand.Name = "labelPublishCommand";
this.labelPublishCommand.Size = new System.Drawing.Size(109, 15);
this.labelPublishCommand.TabIndex = 0;
this.labelPublishCommand.Text = "Publish Command:";
//
// BuildPublishPanel
//
this.AccessibleName = "Building and Publishing";
this.Controls.Add(this.groupBoxPublishing);
this.Controls.Add(this.groupBoxBuilding);
this.Controls.Add(this.groupBoxGeneral);
this.Name = "BuildPublishPanel";
this.PanelName = "Building and Publishing";
this.Size = new System.Drawing.Size(370, 425);
this.Controls.SetChildIndex(this.groupBoxGeneral, 0);
this.Controls.SetChildIndex(this.groupBoxBuilding, 0);
this.Controls.SetChildIndex(this.groupBoxPublishing, 0);
this.groupBoxGeneral.ResumeLayout(false);
this.groupBoxGeneral.PerformLayout();
((System.ComponentModel.ISupportInitialize)(this.numericUpDownCmdTimeout)).EndInit();
this.groupBoxBuilding.ResumeLayout(false);
this.groupBoxBuilding.PerformLayout();
this.groupBoxPublishing.ResumeLayout(false);
this.groupBoxPublishing.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private void RecomputeEnabledStates()
{
textBoxOutputPath.Enabled
= textBoxBuildCommand.Enabled
= labelOutputPath.Enabled
= labelBuildCommand.Enabled
= checkBoxBuildingEnabled.Checked;
labelCmdTimeout.Enabled
= numericUpDownCmdTimeout.Enabled
= checkBoxEnableCmdTimeout.Checked;
}
private void CheckBoxEnableCmdTimeout_CheckedChanged(object sender, EventArgs e)
=> RecomputeEnabledStates();
private void CheckBoxBuildingEnabled_CheckedChanged(object sender, EventArgs e)
=> RecomputeEnabledStates();
}
}

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -0,0 +1,244 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.Controls;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
using OpenLiveWriter.PostEditor;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.PostEditor.Configuration.Wizard;
using KeyIdentifier = OpenLiveWriter.BlogClient.Clients.StaticSite.StaticSiteConfigFrontMatterKeys.KeyIdentifier;
namespace OpenLiveWriter.PostEditor.Configuration.StaticSiteAdvanced
{
/// <summary>
/// Summary description for AccountPanel.
/// </summary>
public class FrontMatterPanel : StaticSitePreferencesPanel
{
private DataGridView dataGridView;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
private DataGridViewTextBoxColumn colProperty;
private DataGridViewTextBoxColumn colKey;
private Label labelSubtitle;
private Button buttonResetDefaults;
private Dictionary<KeyIdentifier, DataGridViewRow> _keyRowMap = new Dictionary<KeyIdentifier, DataGridViewRow>();
public FrontMatterPanel() : base()
{
InitializeComponent();
LocalizeStrings();
}
public FrontMatterPanel(StaticSitePreferencesController controller)
: base(controller)
{
InitializeComponent();
LocalizeStrings();
}
private void LocalizeStrings()
{
PanelName = Res.Get(StringId.SSGConfigFrontMatterTitle);
labelSubtitle.Text = Res.Get(StringId.SSGConfigFrontMatterSubtitle);
colKey.HeaderText = Res.Get(StringId.SSGConfigFrontMatterKeyCol);
colProperty.HeaderText = Res.Get(StringId.SSGConfigFrontMatterPropertyCol);
buttonResetDefaults.Text = Res.Get(StringId.SSGConfigFrontMatterReset);
}
public override void LoadConfig()
{
Keys = _controller.Config.FrontMatterKeys;
}
public override void ValidateConfig()
{
// No validator for FrontMatterKeys yet
}
public override void Save()
{
_controller.Config.FrontMatterKeys = Keys;
}
protected override void OnLayout(LayoutEventArgs e)
{
base.OnLayout(e);
dataGridView?.AutoResizeRows();
}
/// <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);
}
private void AddTableRow(KeyIdentifier keyIdentifier, string prop, string key)
{
_keyRowMap[keyIdentifier] = new DataGridViewRow();
_keyRowMap[keyIdentifier].Cells.Add(new DataGridViewTextBoxCell()
{
Value = prop
});
_keyRowMap[keyIdentifier].Cells.Add(new DataGridViewTextBoxCell()
{
Value = key
});
dataGridView.Rows.Add(_keyRowMap[keyIdentifier]);
}
private string GetTableRow(KeyIdentifier keyIdentifier)
=> _keyRowMap[keyIdentifier].Cells[1].Value as string;
public StaticSiteConfigFrontMatterKeys Keys
{
get => new StaticSiteConfigFrontMatterKeys()
{
IdKey = GetTableRow(KeyIdentifier.Id),
TitleKey = GetTableRow(KeyIdentifier.Title),
DateKey = GetTableRow(KeyIdentifier.Date),
LayoutKey = GetTableRow(KeyIdentifier.Layout),
TagsKey = GetTableRow(KeyIdentifier.Tags),
PermalinkKey = GetTableRow(KeyIdentifier.Permalink),
ParentIdKey = GetTableRow(KeyIdentifier.ParentId)
};
set
{
_keyRowMap = new Dictionary<KeyIdentifier, DataGridViewRow>();
dataGridView.Rows.Clear();
AddTableRow(KeyIdentifier.Id, Res.Get(StringId.SSGFrontMatterId), value.IdKey);
AddTableRow(KeyIdentifier.Title, Res.Get(StringId.SSGFrontMatterTitle), value.TitleKey);
AddTableRow(KeyIdentifier.Date, Res.Get(StringId.SSGFrontMatterDate), value.DateKey);
AddTableRow(KeyIdentifier.Layout, Res.Get(StringId.SSGFrontMatterLayout), value.LayoutKey);
AddTableRow(KeyIdentifier.Tags, Res.Get(StringId.SSGFrontMatterTags), value.TagsKey);
AddTableRow(KeyIdentifier.Permalink, Res.Get(StringId.SSGFrontMatterPermalink), value.PermalinkKey);
AddTableRow(KeyIdentifier.ParentId, Res.Get(StringId.SSGFrontMatterParentId), value.ParentIdKey);
// Recompute row sizes
dataGridView?.AutoResizeRows();
}
}
#region Component 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.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
this.dataGridView = new System.Windows.Forms.DataGridView();
this.colProperty = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.colKey = new System.Windows.Forms.DataGridViewTextBoxColumn();
this.labelSubtitle = new System.Windows.Forms.Label();
this.buttonResetDefaults = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.dataGridView)).BeginInit();
this.SuspendLayout();
//
// dataGridView
//
this.dataGridView.AllowUserToAddRows = false;
this.dataGridView.AllowUserToDeleteRows = false;
this.dataGridView.AllowUserToResizeColumns = false;
this.dataGridView.AllowUserToResizeRows = false;
this.dataGridView.BackgroundColor = System.Drawing.SystemColors.Window;
this.dataGridView.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
this.dataGridView.ColumnHeadersBorderStyle = System.Windows.Forms.DataGridViewHeaderBorderStyle.Single;
this.dataGridView.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dataGridView.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.colProperty,
this.colKey});
this.dataGridView.Location = new System.Drawing.Point(12, 70);
this.dataGridView.MultiSelect = false;
this.dataGridView.Name = "dataGridView";
this.dataGridView.RowHeadersVisible = false;
this.dataGridView.Size = new System.Drawing.Size(350, 323);
this.dataGridView.TabIndex = 2;
//
// colProperty
//
this.colProperty.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.colProperty.DefaultCellStyle = dataGridViewCellStyle1;
this.colProperty.HeaderText = "Property";
this.colProperty.Name = "colProperty";
this.colProperty.ReadOnly = true;
this.colProperty.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
//
// colKey
//
this.colKey.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
this.colKey.HeaderText = "Front Matter Key";
this.colKey.Name = "colKey";
this.colKey.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
//
// labelSubtitle
//
this.labelSubtitle.Location = new System.Drawing.Point(9, 31);
this.labelSubtitle.Name = "labelSubtitle";
this.labelSubtitle.Size = new System.Drawing.Size(353, 36);
this.labelSubtitle.TabIndex = 1;
this.labelSubtitle.Text = "Below you can adjust the post front matter keys used to match your static site ge" +
"nerator.";
//
// buttonResetDefaults
//
this.buttonResetDefaults.Location = new System.Drawing.Point(253, 399);
this.buttonResetDefaults.Name = "buttonResetDefaults";
this.buttonResetDefaults.Size = new System.Drawing.Size(109, 23);
this.buttonResetDefaults.TabIndex = 3;
this.buttonResetDefaults.Text = "Reset to Defaults";
this.buttonResetDefaults.UseVisualStyleBackColor = true;
this.buttonResetDefaults.Click += new System.EventHandler(this.ButtonResetDefaults_Click);
//
// FrontMatterPanel
//
this.AccessibleName = "Front Matter";
this.Controls.Add(this.buttonResetDefaults);
this.Controls.Add(this.labelSubtitle);
this.Controls.Add(this.dataGridView);
this.Name = "FrontMatterPanel";
this.PanelName = "Front Matter";
this.Size = new System.Drawing.Size(370, 425);
this.Controls.SetChildIndex(this.dataGridView, 0);
this.Controls.SetChildIndex(this.labelSubtitle, 0);
this.Controls.SetChildIndex(this.buttonResetDefaults, 0);
((System.ComponentModel.ISupportInitialize)(this.dataGridView)).EndInit();
this.ResumeLayout(false);
}
#endregion
private void ButtonResetDefaults_Click(object sender, EventArgs e)
=> Keys = new StaticSiteConfigFrontMatterKeys();
}
}

View File

@ -0,0 +1,126 @@
<?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="colProperty.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
<metadata name="colKey.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
<value>True</value>
</metadata>
</root>

View File

@ -0,0 +1,334 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Data;
using System.Windows.Forms;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.Controls;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.PostEditor;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.PostEditor.Configuration.Wizard;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
namespace OpenLiveWriter.PostEditor.Configuration.StaticSiteAdvanced
{
/// <summary>
/// Summary description for AccountPanel.
/// </summary>
public class GeneralPanel : StaticSitePreferencesPanel
{
private System.Windows.Forms.GroupBox groupBoxSetup;
private TextBox textBoxSiteTitle;
private Label labelSiteTitle;
private TextBox textBoxLocalSitePath;
private Label labelLocalSitePath;
private TextBox textBoxSiteUrl;
private Button buttonRunAccountWizard;
private GroupBox groupBoxOptions;
private Label labelAutoDetect;
private Button buttonRunAutoDetect;
private Label labelRunWizardAgain;
private Label labelSiteUrl;
private Button buttonBrowseLocalSitePath;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public string SiteTitle
{
get => textBoxSiteTitle.Text;
set => textBoxSiteTitle.Text = value;
}
public string SiteUrl
{
get => textBoxSiteUrl.Text;
set => textBoxSiteUrl.Text = value;
}
public string LocalSitePath
{
get => textBoxLocalSitePath.Text;
set => textBoxLocalSitePath.Text = value;
}
public GeneralPanel() : base()
{
InitializeComponent();
LocalizeStrings();
}
public GeneralPanel(StaticSitePreferencesController controller)
: base(controller)
{
InitializeComponent();
LocalizeStrings();
}
private void LocalizeStrings()
{
PanelName = Res.Get(StringId.SSGConfigGeneralTitle);
groupBoxSetup.Text = Res.Get(StringId.SSGConfigGeneralSetupGroup);
labelSiteTitle.Text = Res.Get(StringId.SSGConfigGeneralSiteTitle);
labelSiteUrl.Text = Res.Get(StringId.SSGConfigGeneralSiteUrl);
labelLocalSitePath.Text = Res.Get(StringId.SSGConfigGeneralLocalSitePath);
groupBoxOptions.Text = Res.Get(StringId.SSGConfigGeneralOptionsGroup);
labelRunWizardAgain.Text = Res.Get(StringId.SSGConfigGeneralWizardLabel);
buttonRunAccountWizard.Text = Res.Get(StringId.SSGConfigGeneralWizardButton);
labelAutoDetect.Text = Res.Get(StringId.SSGConfigGeneralDetectLabel);
buttonRunAutoDetect.Text = Res.Get(StringId.SSGConfigGeneralDetectButton);
}
public override void LoadConfig()
{
SiteTitle = _controller.Config.SiteTitle;
SiteUrl = _controller.Config.SiteUrl;
LocalSitePath = _controller.Config.LocalSitePath;
}
public override void ValidateConfig()
=> _controller.Config.Validator.ValidateLocalSitePath();
public override void Save()
{
_controller.Config.SiteTitle = SiteTitle;
_controller.Config.SiteUrl = SiteUrl;
_controller.Config.LocalSitePath = LocalSitePath;
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
DisplayHelper.AutoFitSystemButton(buttonRunAccountWizard);
DisplayHelper.AutoFitSystemButton(buttonRunAutoDetect);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelRunWizardAgain, buttonRunAccountWizard);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelAutoDetect, buttonRunAutoDetect);
LayoutHelper.DistributeVerticallyNoScale(10,
new ControlGroup(
labelRunWizardAgain,
buttonRunAccountWizard),
new ControlGroup(
labelAutoDetect,
buttonRunAutoDetect));
}
/// <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 Component 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(GeneralPanel));
this.groupBoxSetup = new System.Windows.Forms.GroupBox();
this.buttonBrowseLocalSitePath = new System.Windows.Forms.Button();
this.textBoxLocalSitePath = new System.Windows.Forms.TextBox();
this.labelLocalSitePath = new System.Windows.Forms.Label();
this.textBoxSiteUrl = new System.Windows.Forms.TextBox();
this.labelSiteUrl = new System.Windows.Forms.Label();
this.textBoxSiteTitle = new System.Windows.Forms.TextBox();
this.labelSiteTitle = new System.Windows.Forms.Label();
this.buttonRunAccountWizard = new System.Windows.Forms.Button();
this.groupBoxOptions = new System.Windows.Forms.GroupBox();
this.labelAutoDetect = new System.Windows.Forms.Label();
this.buttonRunAutoDetect = new System.Windows.Forms.Button();
this.labelRunWizardAgain = new System.Windows.Forms.Label();
this.groupBoxSetup.SuspendLayout();
this.groupBoxOptions.SuspendLayout();
this.SuspendLayout();
//
// groupBoxSetup
//
this.groupBoxSetup.Controls.Add(this.buttonBrowseLocalSitePath);
this.groupBoxSetup.Controls.Add(this.textBoxLocalSitePath);
this.groupBoxSetup.Controls.Add(this.labelLocalSitePath);
this.groupBoxSetup.Controls.Add(this.textBoxSiteUrl);
this.groupBoxSetup.Controls.Add(this.labelSiteUrl);
this.groupBoxSetup.Controls.Add(this.textBoxSiteTitle);
this.groupBoxSetup.Controls.Add(this.labelSiteTitle);
this.groupBoxSetup.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.groupBoxSetup.Location = new System.Drawing.Point(8, 32);
this.groupBoxSetup.Name = "groupBoxSetup";
this.groupBoxSetup.Size = new System.Drawing.Size(345, 158);
this.groupBoxSetup.TabIndex = 1;
this.groupBoxSetup.TabStop = false;
this.groupBoxSetup.Text = "Setup";
//
// buttonBrowseLocalSitePath
//
this.buttonBrowseLocalSitePath.Location = new System.Drawing.Point(308, 125);
this.buttonBrowseLocalSitePath.Name = "buttonBrowseLocalSitePath";
this.buttonBrowseLocalSitePath.Size = new System.Drawing.Size(24, 24);
this.buttonBrowseLocalSitePath.TabIndex = 6;
this.buttonBrowseLocalSitePath.Text = "...";
this.buttonBrowseLocalSitePath.UseVisualStyleBackColor = true;
this.buttonBrowseLocalSitePath.Click += new System.EventHandler(this.ButtonBrowseLocalSitePath_Click);
//
// textBoxLocalSitePath
//
this.textBoxLocalSitePath.Location = new System.Drawing.Point(16, 126);
this.textBoxLocalSitePath.Name = "textBoxLocalSitePath";
this.textBoxLocalSitePath.Size = new System.Drawing.Size(286, 23);
this.textBoxLocalSitePath.TabIndex = 5;
//
// labelLocalSitePath
//
this.labelLocalSitePath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelLocalSitePath.Location = new System.Drawing.Point(16, 107);
this.labelLocalSitePath.Name = "labelLocalSitePath";
this.labelLocalSitePath.Size = new System.Drawing.Size(144, 16);
this.labelLocalSitePath.TabIndex = 4;
this.labelLocalSitePath.Text = "&Local Site Path:";
//
// textBoxSiteUrl
//
this.textBoxSiteUrl.Location = new System.Drawing.Point(16, 81);
this.textBoxSiteUrl.Name = "textBoxSiteUrl";
this.textBoxSiteUrl.Size = new System.Drawing.Size(316, 23);
this.textBoxSiteUrl.TabIndex = 3;
//
// labelSiteUrl
//
this.labelSiteUrl.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelSiteUrl.Location = new System.Drawing.Point(16, 63);
this.labelSiteUrl.Name = "labelSiteUrl";
this.labelSiteUrl.Size = new System.Drawing.Size(144, 16);
this.labelSiteUrl.TabIndex = 2;
this.labelSiteUrl.Text = "Site &URL:";
//
// textBoxSiteTitle
//
this.textBoxSiteTitle.Location = new System.Drawing.Point(16, 37);
this.textBoxSiteTitle.Name = "textBoxSiteTitle";
this.textBoxSiteTitle.Size = new System.Drawing.Size(316, 23);
this.textBoxSiteTitle.TabIndex = 1;
//
// labelSiteTitle
//
this.labelSiteTitle.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelSiteTitle.Location = new System.Drawing.Point(16, 19);
this.labelSiteTitle.Name = "labelSiteTitle";
this.labelSiteTitle.Size = new System.Drawing.Size(144, 16);
this.labelSiteTitle.TabIndex = 0;
this.labelSiteTitle.Text = "Site &Title:";
//
// buttonRunAccountWizard
//
this.buttonRunAccountWizard.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonRunAccountWizard.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.buttonRunAccountWizard.Location = new System.Drawing.Point(188, 70);
this.buttonRunAccountWizard.Name = "buttonRunAccountWizard";
this.buttonRunAccountWizard.Size = new System.Drawing.Size(144, 23);
this.buttonRunAccountWizard.TabIndex = 1;
this.buttonRunAccountWizard.Text = "Run Account &Wizard";
this.buttonRunAccountWizard.Click += new System.EventHandler(this.ButtonRunAccountWizard_Click);
//
// groupBoxOptions
//
this.groupBoxOptions.Controls.Add(this.labelAutoDetect);
this.groupBoxOptions.Controls.Add(this.buttonRunAutoDetect);
this.groupBoxOptions.Controls.Add(this.labelRunWizardAgain);
this.groupBoxOptions.Controls.Add(this.buttonRunAccountWizard);
this.groupBoxOptions.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.groupBoxOptions.Location = new System.Drawing.Point(8, 196);
this.groupBoxOptions.Name = "groupBoxOptions";
this.groupBoxOptions.Size = new System.Drawing.Size(345, 226);
this.groupBoxOptions.TabIndex = 2;
this.groupBoxOptions.TabStop = false;
this.groupBoxOptions.Text = "Options";
//
// labelAutoDetect
//
this.labelAutoDetect.Location = new System.Drawing.Point(13, 96);
this.labelAutoDetect.Name = "labelAutoDetect";
this.labelAutoDetect.Size = new System.Drawing.Size(316, 75);
this.labelAutoDetect.TabIndex = 2;
this.labelAutoDetect.Text = resources.GetString("labelAutoDetect.Text");
//
// buttonRunAutoDetect
//
this.buttonRunAutoDetect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.buttonRunAutoDetect.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.buttonRunAutoDetect.Location = new System.Drawing.Point(188, 174);
this.buttonRunAutoDetect.Name = "buttonRunAutoDetect";
this.buttonRunAutoDetect.Size = new System.Drawing.Size(144, 23);
this.buttonRunAutoDetect.TabIndex = 3;
this.buttonRunAutoDetect.Text = "Run Auto-&Detect";
this.buttonRunAutoDetect.Click += new System.EventHandler(this.ButtonRunAutoDetect_Click);
//
// labelRunWizardAgain
//
this.labelRunWizardAgain.Location = new System.Drawing.Point(16, 19);
this.labelRunWizardAgain.Name = "labelRunWizardAgain";
this.labelRunWizardAgain.Size = new System.Drawing.Size(316, 48);
this.labelRunWizardAgain.TabIndex = 0;
this.labelRunWizardAgain.Text = "You can chose to run the Account Wizard again if you wish to be guided through th" +
"e core static site configuration options interactively.";
//
// GeneralPanel
//
this.AccessibleName = "General";
this.Controls.Add(this.groupBoxSetup);
this.Controls.Add(this.groupBoxOptions);
this.Name = "GeneralPanel";
this.PanelName = "General";
this.Size = new System.Drawing.Size(370, 425);
this.Controls.SetChildIndex(this.groupBoxOptions, 0);
this.Controls.SetChildIndex(this.groupBoxSetup, 0);
this.groupBoxSetup.ResumeLayout(false);
this.groupBoxSetup.PerformLayout();
this.groupBoxOptions.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
private void ButtonRunAccountWizard_Click(object sender, EventArgs e)
=> _controller.GeneralPanel_RunAccountWizard();
private void ButtonRunAutoDetect_Click(object sender, EventArgs e)
=> _controller.GeneralPanel_RunAutoDetect();
private void ButtonBrowseLocalSitePath_Click(object sender, EventArgs e)
{
var folderBrowserDialog = new FolderBrowserDialog();
folderBrowserDialog.ShowNewFolderButton = false;
folderBrowserDialog.Description = Res.Get(StringId.CWStaticSiteLocalSiteFolderPicker);
var result = folderBrowserDialog.ShowDialog();
if (result == DialogResult.OK)
{
textBoxLocalSitePath.Text = folderBrowserDialog.SelectedPath;
}
}
}
}

View File

@ -0,0 +1,123 @@
<?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>
<data name="labelAutoDetect.Text" xml:space="preserve">
<value>Open Live Writer can also reattempt to detect relevant configuration options for your static site. This may not result in a complete configuration, so please use the fields on the following pages to ensure all settings are set correctly.</value>
</data>
</root>

View File

@ -0,0 +1,142 @@
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
using OpenLiveWriter.Localization;
using OpenLiveWriter.PostEditor.Configuration.Wizard;
namespace OpenLiveWriter.PostEditor.Configuration.StaticSiteAdvanced
{
public class StaticSitePreferencesController
{
public StaticSiteConfig Config { get; set; }
private TemporaryBlogSettings _temporarySettings;
private StaticSitePreferencesForm formPreferences;
private GeneralPanel panelGeneral;
private AuthoringPanel panelAuthoring;
private FrontMatterPanel panelFrontMatter;
private BuildPublishPanel panelBuildPublish;
public StaticSitePreferencesController(TemporaryBlogSettings blogSettings)
{
_temporarySettings = blogSettings;
LoadConfigFromBlogSettings();
panelGeneral = new GeneralPanel(this);
panelAuthoring = new AuthoringPanel(this);
panelFrontMatter = new FrontMatterPanel(this);
panelBuildPublish = new BuildPublishPanel(this);
LoadConfigIntoPanels();
}
public void LoadConfigFromBlogSettings()
=> Config = StaticSiteConfig.LoadConfigFromBlogSettings(_temporarySettings);
public void LoadConfigIntoPanels()
{
panelGeneral.LoadConfig();
panelAuthoring.LoadConfig();
panelFrontMatter.LoadConfig();
panelBuildPublish.LoadConfig();
}
/// <summary>
/// Save the StaticSiteConfig to an arbitrary TemporaryBlogSettings
/// </summary>
/// <param name="settings">the TemporaryBlogSettings object</param>
private void SaveConfigToBlogSettings(TemporaryBlogSettings settings)
{
Config.SaveToCredentials(settings.Credentials);
settings.BlogName = Config.SiteTitle;
settings.HomepageUrl = Config.SiteUrl;
}
private bool EditWeblogTemporarySettings(IWin32Window owner)
{
LoadConfigFromBlogSettings();
LoadConfigIntoPanels();
// Show form
using (formPreferences = new StaticSitePreferencesForm(this))
{
using (BlogClientUIContextScope uiContextScope = new BlogClientUIContextScope(formPreferences))
{
// Customize form title and behavior
formPreferences.Text = string.Format(Res.Get(StringId.SSGConfigTitle), _temporarySettings.BlogName);
formPreferences.HideApplyButton();
// Add panels
int iPanel = 0;
formPreferences.SetEntry(iPanel++, panelGeneral);
formPreferences.SetEntry(iPanel++, panelAuthoring);
formPreferences.SetEntry(iPanel++, panelFrontMatter);
formPreferences.SetEntry(iPanel++, panelBuildPublish);
formPreferences.SelectedIndex = 0;
// Show the dialog
var result = formPreferences.ShowDialog(owner);
if(result == DialogResult.OK)
{
// All panels should be validated by this point, so save the settings
SaveConfigToBlogSettings(_temporarySettings);
return true;
}
}
}
return false;
}
public void GeneralPanel_RunAccountWizard()
{
var settingsCopy = TemporaryBlogSettings.CreateNew();
settingsCopy.CopyFrom(_temporarySettings);
SaveConfigToBlogSettings(settingsCopy);
var result = WeblogConfigurationWizardController.EditTemporarySettings(formPreferences, settingsCopy);
// If wizard is successful, load the new settings back into the form.
if(result)
{
_temporarySettings.CopyFrom(settingsCopy);
Config = StaticSiteConfig.LoadConfigFromBlogSettings(_temporarySettings);
LoadConfigIntoPanels();
}
}
public void GeneralPanel_RunAutoDetect()
{
var result = StaticSiteConfigDetector.AttmeptAutoDetect(Config);
if(result)
{
// Successful detection of parameters
MessageBox.Show(
string.Format(Res.Get(StringId.CWStaticSiteConfigDetection), Res.Get(StringId.ProductNameVersioned)),
Res.Get(StringId.ProductNameVersioned),
MessageBoxButtons.OK,
MessageBoxIcon.Information);
LoadConfigIntoPanels();
}
}
public static bool EditTemporarySettings(IWin32Window owner, TemporaryBlogSettings settings)
=> new StaticSitePreferencesController(settings).EditWeblogTemporarySettings(owner);
}
}

View File

@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using OpenLiveWriter.ApplicationFramework.Preferences;
namespace OpenLiveWriter.PostEditor.Configuration.StaticSiteAdvanced
{
public class StaticSitePreferencesForm : PreferencesForm
{
private StaticSitePreferencesController _controller;
public StaticSitePreferencesForm(StaticSitePreferencesController controller)
{
_controller = controller;
}
protected override bool SavePreferences()
{
// We need to save all the results before validating them, as the validity of some settings are dependant on others,
// eg. PostPath is dependant on LocalSitePath
// Clone the existing config in-case of validation failure
var originalConfig = _controller.Config.Clone();
foreach (StaticSitePreferencesPanel preferencesPanel in preferencesPanelList)
{
preferencesPanel.Save();
}
// On succesful validation, hand control back to controller
if (base.SavePreferences()) return true;
// Otherwise, reset settings to pre-validation state and return false
_controller.Config = originalConfig;
return false;
}
}
}

View File

@ -0,0 +1,60 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
namespace OpenLiveWriter.PostEditor.Configuration.StaticSiteAdvanced
{
public class StaticSitePreferencesPanel : PreferencesPanel
{
private System.ComponentModel.Container components = null;
protected StaticSitePreferencesController _controller;
public StaticSitePreferencesPanel() : base()
{
// This code should never be called at runtime, is only used for the designer
_controller = null;
}
public StaticSitePreferencesPanel(StaticSitePreferencesController controller) : base()
{
_controller = controller;
}
public virtual void LoadConfig() { }
public virtual void ValidateConfig() { }
public override bool PrepareSave(SwitchToPanel switchToPanel)
{
try
{
ValidateConfig();
}
catch (StaticSiteConfigValidationException ex)
{
MessageBox.Show(ex.Text, ex.Title, MessageBoxButtons.OK, MessageBoxIcon.Warning);
switchToPanel();
return false;
}
return true;
}
/// <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);
}
}
}

View File

@ -36,6 +36,7 @@ namespace OpenLiveWriter.PostEditor.Configuration
tempSettings.IsSpacesBlog = blogSettings.IsSpacesBlog;
tempSettings.IsSharePointBlog = blogSettings.IsSharePointBlog;
tempSettings.IsGoogleBloggerBlog = blogSettings.IsGoogleBloggerBlog;
tempSettings.IsStaticSiteBlog = blogSettings.IsStaticSiteBlog;
tempSettings.HostBlogId = blogSettings.HostBlogId;
tempSettings.BlogName = blogSettings.BlogName;
tempSettings.HomepageUrl = blogSettings.HomepageUrl;
@ -85,6 +86,7 @@ namespace OpenLiveWriter.PostEditor.Configuration
settings.IsSpacesBlog = this.IsSpacesBlog;
settings.IsSharePointBlog = this.IsSharePointBlog;
settings.IsGoogleBloggerBlog = this.IsGoogleBloggerBlog;
settings.IsStaticSiteBlog = this.IsStaticSiteBlog;
settings.BlogName = this.BlogName;
settings.HomepageUrl = this.HomepageUrl;
settings.ForceManualConfig = this.ForceManualConfig;
@ -268,6 +270,19 @@ namespace OpenLiveWriter.PostEditor.Configuration
}
}
public bool IsStaticSiteBlog
{
get
{
return _isStaticSiteBlog;
}
set
{
_isStaticSiteBlog = value;
}
}
public string HostBlogId
{
get
@ -620,6 +635,7 @@ namespace OpenLiveWriter.PostEditor.Configuration
private bool _isSpacesBlog = false;
private bool _isSharePointBlog = false;
private bool _isGoogleBloggerBlog = false;
private bool _isStaticSiteBlog = false;
private string _hostBlogId = String.Empty;
private string _blogName = String.Empty;
private string _homePageUrl = String.Empty;
@ -681,6 +697,7 @@ namespace OpenLiveWriter.PostEditor.Configuration
_isSpacesBlog = sourceSettings._isSpacesBlog;
_isSharePointBlog = sourceSettings._isSharePointBlog;
_isGoogleBloggerBlog = sourceSettings._isGoogleBloggerBlog;
_isStaticSiteBlog = sourceSettings._isStaticSiteBlog;
_hostBlogId = sourceSettings._hostBlogId;
_blogName = sourceSettings._blogName;
_homePageUrl = sourceSettings._homePageUrl;

View File

@ -7,6 +7,7 @@ using System.Diagnostics;
using System.ComponentModel;
using System.Windows.Forms;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
using OpenLiveWriter.BlogClient.Providers;
using OpenLiveWriter.Extensibility.BlogClient;
using OpenLiveWriter.Controls.Wizard;
@ -172,9 +173,15 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
{
// first step conditional on blog type
if (_temporarySettings.IsSharePointBlog)
{
AddSharePointBasicInfoSubStep(true);
else
} else if (_temporarySettings.IsStaticSiteBlog)
{
AddStaticSiteInitialSubStep();
} else
{
AddBasicInfoSubStep();
}
AddConfirmationStep();
@ -337,6 +344,7 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
// set the user's choice
_temporarySettings.IsSharePointBlog = panelBlogType.IsSharePointBlog;
_temporarySettings.IsGoogleBloggerBlog = panelBlogType.IsGoogleBloggerBlog;
_temporarySettings.IsStaticSiteBlog = panelBlogType.IsStaticSiteBlog;
// did this bootstrap a custom account wizard?
_providerAccountWizard = panelBlogType.ProviderAccountWizard;
@ -350,6 +358,10 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
{
AddGoogleBloggerOAuthSubStep();
}
else if (_temporarySettings.IsStaticSiteBlog)
{
AddStaticSiteInitialSubStep();
}
else
{
AddBasicInfoSubStep();
@ -462,6 +474,217 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
#endregion
#region Static Site Generator support
private StaticSiteConfig staticSiteConfig;
private void AddStaticSiteInitialSubStep()
{
addWizardSubStep(
new WizardSubStep(new WeblogConfigurationWizardPanelStaticSiteInitial(),
null,
new DisplayCallback(OnStaticSiteInitialDisplayed),
new VerifyStepCallback(OnStaticSiteValidatePanel),
new NextCallback(OnStaticSiteInitialCompleted),
null,
new BackCallback(OnStaticSiteBack)));
}
private void OnStaticSiteInitialDisplayed(Object stepControl)
{
// Populate data
var panel = (stepControl as WeblogConfigurationWizardPanelStaticSiteInitial);
// Load static config from credentials provided
staticSiteConfig = StaticSiteConfig.LoadConfigFromBlogSettings( _temporarySettings);
panel.LoadFromConfig(staticSiteConfig);
}
private void OnStaticSiteInitialCompleted(Object stepControl)
{
var panel = (stepControl as WeblogConfigurationWizardPanelStaticSiteInitial);
// Fill blog settings
_temporarySettings.SetProvider(
StaticSiteClient.PROVIDER_ID,
StaticSiteClient.SERVICE_NAME,
StaticSiteClient.POST_API_URL,
StaticSiteClient.CLIENT_TYPE
);
// Save config
panel.SaveToConfig(staticSiteConfig);
if(!staticSiteConfig.Initialised)
{
// Set initialised flag so detection isn't undertaken again
staticSiteConfig.Initialised = true;
// Attempt parameter detection
var detectionResult = StaticSiteConfigDetector.AttmeptAutoDetect(staticSiteConfig);
if (detectionResult)
{
// Successful detection of parameters
MessageBox.Show(
string.Format(Res.Get(StringId.CWStaticSiteConfigDetection), Res.Get(StringId.ProductNameVersioned)),
Res.Get(StringId.ProductNameVersioned),
MessageBoxButtons.OK,
MessageBoxIcon.Information);
}
}
// Go to next step
AddStaticSiteFeaturesSubStep();
}
private void AddStaticSitePaths1SubStep()
{
addWizardSubStep(
new WizardSubStep(new WeblogConfigurationWizardPanelStaticSitePaths1(),
null,
new DisplayCallback(OnStaticSiteConfigProviderDisplayed),
new VerifyStepCallback(OnStaticSiteValidatePanel),
new NextCallback(OnStaticSitePaths1Completed),
null,
new BackCallback(OnStaticSiteBack)));
}
private void OnStaticSitePaths1Completed(Object stepControl)
{
var panel = (stepControl as WeblogConfigurationWizardPanelStaticSitePaths1);
// Save panel values into config
panel.SaveToConfig(staticSiteConfig);
// Go to next step
AddStaticSitePaths2SubStep();
}
private void AddStaticSitePaths2SubStep()
{
addWizardSubStep(
new WizardSubStep(new WeblogConfigurationWizardPanelStaticSitePaths2(),
null,
new DisplayCallback(OnStaticSiteConfigProviderDisplayed),
new VerifyStepCallback(OnStaticSiteValidatePanel),
new NextCallback(OnStaticSitePaths2Completed),
null,
new BackCallback(OnStaticSiteBack)));
}
private void OnStaticSitePaths2Completed(Object stepControl)
{
var panel = (stepControl as WeblogConfigurationWizardPanelStaticSitePaths2);
// Save panel values into config
panel.SaveToConfig(staticSiteConfig);
// Go to next step
AddStaticSiteCommandsSubStep();
}
private void AddStaticSiteFeaturesSubStep()
{
addWizardSubStep(
new WizardSubStep(new WeblogConfigurationWizardPanelStaticSiteFeatures(),
null,
new DisplayCallback(OnStaticSiteConfigProviderDisplayed),
new VerifyStepCallback(OnStaticSiteValidatePanel),
new NextCallback(OnStaticSiteFeaturesCompleted),
null,
new BackCallback(OnStaticSiteBack)));
}
private void OnStaticSiteFeaturesCompleted(Object stepControl)
{
var panel = (stepControl as WeblogConfigurationWizardPanelStaticSiteFeatures);
// Save panel values into config
panel.SaveToConfig(staticSiteConfig);
// Go to next step
AddStaticSitePaths1SubStep();
}
private void AddStaticSiteCommandsSubStep()
{
addWizardSubStep(
new WizardSubStep(new WeblogConfigurationWizardPanelStaticSiteCommands(),
null,
new DisplayCallback(OnStaticSiteConfigProviderDisplayed),
new VerifyStepCallback(OnStaticSiteValidatePanel),
new NextCallback(OnStaticSiteCommandsCompleted),
null,
new BackCallback(OnStaticSiteBack)));
}
private void OnStaticSiteCommandsCompleted(Object stepControl)
{
var panel = (stepControl as WeblogConfigurationWizardPanelStaticSiteCommands);
// Save panel values into config
panel.SaveToConfig(staticSiteConfig);
// Go to next step
PerformStaticSiteWizardCompletion();
}
private void OnStaticSiteBack(object step)
{
// Save panel values before going back
(step as IWizardPanelStaticSite).SaveToConfig(staticSiteConfig);
}
private bool OnStaticSiteValidatePanel(object step)
{
var newConfig = staticSiteConfig.Clone();
IWizardPanelStaticSite panel = step as IWizardPanelStaticSite;
panel.SaveToConfig(newConfig);
try
{
panel.ValidateWithConfig(newConfig);
} catch(StaticSiteConfigValidationException ex)
{
MessageBox.Show(ex.Text, ex.Title, MessageBoxButtons.OK, MessageBoxIcon.Warning);
return false;
}
return true;
}
private void PerformStaticSiteWizardCompletion()
{
// Fill blog settings
_temporarySettings.SetProvider(
StaticSiteClient.PROVIDER_ID,
StaticSiteClient.SERVICE_NAME,
StaticSiteClient.POST_API_URL,
StaticSiteClient.CLIENT_TYPE
);
_temporarySettings.HomepageUrl = staticSiteConfig.SiteUrl;
_temporarySettings.BlogName = staticSiteConfig.SiteTitle;
// Fill config into credentials
staticSiteConfig.SaveToCredentials(_temporarySettings.Credentials);
// Perform auto-detection
addWizardSubStep(new WizardAutoDetectionStep(
(IBlogClientUIContext)this,
_temporarySettings,
null,
new WizardSettingsAutoDetectionOperation(_editWithStyleStep)));
}
private void OnStaticSiteConfigProviderDisplayed(Object stepControl)
{
// Populate data
var panel = (stepControl as IWizardPanelStaticSite);
// Load panel values from config
panel.LoadFromConfig(staticSiteConfig);
}
#endregion
#region Weblog and Settings Auto Detection
private void PerformWeblogAndSettingsAutoDetectionSubStep()
@ -812,6 +1035,27 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
}
internal interface IWizardPanelStaticSite
{
/// <summary>
/// Validate the relevant parts of the Static Site Config, raising an exception if the configuration is invalid.
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
void ValidateWithConfig(StaticSiteConfig config);
/// <summary>
/// Saves panel form fields into a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
void SaveToConfig(StaticSiteConfig config);
/// <summary>
/// Loads panel form fields from a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
void LoadFromConfig(StaticSiteConfig config);
}
internal interface IAccountBasicInfoProvider
{
IBlogProviderAccountWizardDescription ProviderAccountWizard { set; }

View File

@ -28,6 +28,11 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
WPCreate = 29,
SharePointBasicInfo = 32,
OtherBasicInfo = 33,
StaticSiteConfigInitial = 34,
StaticSiteConfigCapabilities = 35,
StaticSiteConfigPaths1 = 36,
StaticSiteConfigPaths2 = 37,
StaticSiteConfigCommands = 38,
SelectProvider = 40,
SharePointAuth = 41,
GoogleBloggerAuth = 42,

View File

@ -26,6 +26,7 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
private System.Windows.Forms.Panel panelRadioButtons;
private System.Windows.Forms.RadioButton radioButtonSharePoint;
private System.Windows.Forms.RadioButton radioButtonBlogger;
private System.Windows.Forms.RadioButton radioButtonStaticSite;
private System.Windows.Forms.RadioButton radioButtonOther;
private System.Windows.Forms.Label labelOtherDesc;
private System.Windows.Forms.RadioButton radioButtonWordpress;
@ -51,6 +52,7 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
radioButtonBlogger.Text = Res.Get(StringId.WizardBlogTypeGoogleBlogger);
radioButtonOther.Text = Res.Get(StringId.WizardBlogTypeOther);
radioButtonWordpress.Text = Res.Get(StringId.CWWelcomeWP);
radioButtonStaticSite.Text = Res.Get(StringId.WizardBlogTypeStaticSite);
labelOtherDesc.Text = Res.Get(StringId.BlogServiceNames);
@ -82,6 +84,8 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
comboBoxSelectWeblogType.Items.Add(new WeblogType(radioButtonBlogger));
comboBoxSelectWeblogType.Items.Add(new WeblogType(radioButtonStaticSite));
// add "another weblog type" entry
comboBoxSelectWeblogType.Items.Add(new WeblogType(radioButtonOther));
@ -104,6 +108,7 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
radioButtonWordpress.CheckedChanged += new EventHandler(UserChangedSelectionHandler);
radioButtonSharePoint.CheckedChanged += new EventHandler(UserChangedSelectionHandler);
radioButtonBlogger.CheckedChanged += new EventHandler(UserChangedSelectionHandler);
radioButtonStaticSite.CheckedChanged += new EventHandler(UserChangedSelectionHandler);
radioButtonOther.CheckedChanged += new EventHandler(UserChangedSelectionHandler);
}
@ -122,12 +127,18 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
MaximizeWidth(radioButtonWordpress);
MaximizeWidth(radioButtonSharePoint);
MaximizeWidth(radioButtonBlogger);
MaximizeWidth(radioButtonStaticSite);
MaximizeWidth(radioButtonOther);
MaximizeWidth(labelOtherDesc);
using (new AutoGrow(panelRadioButtons, AnchorStyles.Bottom, true))
{
LayoutHelper.NaturalizeHeightAndDistribute(3, radioButtonWordpress, radioButtonSharePoint, radioButtonBlogger, radioButtonOther);
LayoutHelper.NaturalizeHeightAndDistribute(3,
radioButtonWordpress,
radioButtonSharePoint,
radioButtonBlogger,
radioButtonStaticSite,
radioButtonOther);
labelOtherDesc.Top = radioButtonOther.Bottom;
}
}
@ -182,6 +193,21 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
}
}
public bool IsStaticSiteBlog
{
get
{
if (panelRadioButtons.Visible)
{
return radioButtonStaticSite.Checked;
}
else
{
return SelectedWeblog.RadioButton == radioButtonStaticSite;
}
}
}
public IBlogProviderAccountWizardDescription ProviderAccountWizard
{
get
@ -314,6 +340,7 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
this.radioButtonWordpress = new System.Windows.Forms.RadioButton();
this.radioButtonSharePoint = new System.Windows.Forms.RadioButton();
this.radioButtonBlogger = new System.Windows.Forms.RadioButton();
this.radioButtonStaticSite = new System.Windows.Forms.RadioButton();
this.radioButtonOther = new System.Windows.Forms.RadioButton();
this.labelOtherDesc = new System.Windows.Forms.Label();
this.panelComboBox = new System.Windows.Forms.Panel();
@ -348,6 +375,7 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
this.panelRadioButtons.Controls.Add(this.radioButtonWordpress);
this.panelRadioButtons.Controls.Add(this.radioButtonSharePoint);
this.panelRadioButtons.Controls.Add(this.radioButtonBlogger);
this.panelRadioButtons.Controls.Add(this.radioButtonStaticSite);
this.panelRadioButtons.Controls.Add(this.radioButtonOther);
this.panelRadioButtons.Controls.Add(this.labelOtherDesc);
this.panelRadioButtons.Location = new System.Drawing.Point(20, 88);
@ -380,12 +408,20 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
this.radioButtonBlogger.TabIndex = 3;
this.radioButtonBlogger.Text = "&Google Blogger";
//
// radioButtonStaticSite
//
this.radioButtonBlogger.Location = new System.Drawing.Point(0, 72);
this.radioButtonBlogger.Name = "radioButtonStaticSite";
this.radioButtonBlogger.Size = new System.Drawing.Size(104, 24);
this.radioButtonBlogger.TabIndex = 4;
this.radioButtonBlogger.Text = "Static Site G&enerator";
//
// radioButtonOther
//
this.radioButtonOther.Location = new System.Drawing.Point(0, 48);
this.radioButtonOther.Name = "radioButtonOther";
this.radioButtonOther.Size = new System.Drawing.Size(104, 24);
this.radioButtonOther.TabIndex = 4;
this.radioButtonOther.TabIndex = 5;
this.radioButtonOther.Text = "Another &weblog service";
//
// labelOtherDesc
@ -395,7 +431,7 @@ namespace OpenLiveWriter.PostEditor.Configuration.Wizard
this.labelOtherDesc.Location = new System.Drawing.Point(18, 0);
this.labelOtherDesc.Name = "labelOtherDesc";
this.labelOtherDesc.Size = new System.Drawing.Size(332, 40);
this.labelOtherDesc.TabIndex = 5;
this.labelOtherDesc.TabIndex = 6;
this.labelOtherDesc.Text = "TypePad and others";
//
// panelComboBox

View File

@ -0,0 +1,258 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.IO;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.Extensibility.BlogClient;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.PostEditor.BlogProviderButtons;
namespace OpenLiveWriter.PostEditor.Configuration.Wizard
{
/// <summary>
/// Summary description for WelcomeToBlogControl.
/// </summary>
internal class WeblogConfigurationWizardPanelStaticSiteCommands : WeblogConfigurationWizardPanel, IWizardPanelStaticSite
{
private Label labelPublishCommand;
private TextBox textBoxBuildCommand;
private Label labelBuildCommand;
private TextBox textBoxPublishCommand;
/// <summary>
/// Local site path, loaded from config, used for validation
/// </summary>
private string _localSitePath;
private Label labelSubtitle;
private Label labelPublishCommandSubtitle;
private Label labelBuildCommandSubtitle;
/// <summary>
/// Required designer variable.
/// </summary>
private Container components = null;
public WeblogConfigurationWizardPanelStaticSiteCommands()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
labelHeader.Text = Res.Get(StringId.CWStaticSiteCommandsTitle);
labelSubtitle.Text = string.Format(Res.Get(StringId.CWStaticSiteCommandsSubtitle), Res.Get(StringId.ProductNameVersioned));
labelBuildCommand.Text = Res.Get(StringId.CWStaticSiteCommandsBuildCommand);
labelBuildCommandSubtitle.Text = Res.Get(StringId.CWStaticSiteCommandsBuildCommandSubtitle);
labelPublishCommand.Text = Res.Get(StringId.CWStaticSiteCommandsPublishCommand);
labelPublishCommandSubtitle.Text = Res.Get(StringId.CWStaticSiteCommandsPublishCommandSubtitle);
labelBuildCommandSubtitle.ForeColor = labelPublishCommandSubtitle.ForeColor =
!SystemInformation.HighContrast ? Color.FromArgb(136, 136, 136) : SystemColors.GrayText;
}
public override void NaturalizeLayout()
{
// Wizard views are very broken in the VS Form Designer, due to runtime control layout.
if (DesignMode) return;
MaximizeWidth(labelSubtitle);
MaximizeWidth(labelBuildCommand);
MaximizeWidth(labelBuildCommandSubtitle);
MaximizeWidth(textBoxBuildCommand);
MaximizeWidth(labelPublishCommand);
MaximizeWidth(labelPublishCommandSubtitle);
MaximizeWidth(textBoxPublishCommand);
LayoutHelper.NaturalizeHeight(labelSubtitle);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelBuildCommand, labelBuildCommandSubtitle, textBoxBuildCommand);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelPublishCommand, labelPublishCommandSubtitle, textBoxPublishCommand);
LayoutHelper.DistributeVerticallyNoScale(10, false,
labelSubtitle,
new ControlGroup(labelBuildCommand, labelBuildCommandSubtitle, textBoxBuildCommand),
new ControlGroup(labelPublishCommand, labelPublishCommandSubtitle, textBoxPublishCommand)
);
}
public override ConfigPanelId? PanelId
{
get { return ConfigPanelId.StaticSiteConfigCommands; }
}
public string PublishCommand
{
get => textBoxPublishCommand.Text;
set => textBoxPublishCommand.Text = value;
}
private bool _buildingEnabled = false;
public bool BuildingEnabled
{
get => _buildingEnabled;
set => _buildingEnabled
= labelBuildCommand.Enabled
= labelBuildCommandSubtitle.Enabled
= textBoxBuildCommand.Enabled
= value;
}
public string BuildCommand
{
get => textBoxBuildCommand.Text;
set => textBoxBuildCommand.Text = value;
}
public void ValidateWithConfig(StaticSiteConfig config)
=> config.Validator
.ValidateBuildCommand()
.ValidatePublishCommand();
/// <summary>
/// Saves panel form fields into a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void SaveToConfig(StaticSiteConfig config)
{
config.BuildCommand = BuildCommand;
config.PublishCommand = PublishCommand;
}
/// <summary>
/// Loads panel form fields from a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void LoadFromConfig(StaticSiteConfig config)
{
_localSitePath = config.LocalSitePath;
BuildingEnabled = config.BuildingEnabled;
BuildCommand = config.BuildCommand;
PublishCommand = config.PublishCommand;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Component 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()
{
this.labelPublishCommand = new System.Windows.Forms.Label();
this.textBoxBuildCommand = new System.Windows.Forms.TextBox();
this.labelBuildCommand = new System.Windows.Forms.Label();
this.textBoxPublishCommand = new System.Windows.Forms.TextBox();
this.labelSubtitle = new System.Windows.Forms.Label();
this.labelBuildCommandSubtitle = new System.Windows.Forms.Label();
this.labelPublishCommandSubtitle = new System.Windows.Forms.Label();
this.panelMain.SuspendLayout();
this.SuspendLayout();
//
// panelMain
//
this.panelMain.Controls.Add(this.labelSubtitle);
this.panelMain.Controls.Add(this.labelBuildCommand);
this.panelMain.Controls.Add(this.labelBuildCommandSubtitle);
this.panelMain.Controls.Add(this.textBoxBuildCommand);
this.panelMain.Controls.Add(this.labelPublishCommandSubtitle);
this.panelMain.Controls.Add(this.labelPublishCommand);
this.panelMain.Controls.Add(this.textBoxPublishCommand);
this.panelMain.Size = new System.Drawing.Size(435, 242);
//
// labelPublishCommand
//
this.labelPublishCommand.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelPublishCommand.Location = new System.Drawing.Point(20, 125);
this.labelPublishCommand.Name = "labelPublishCommand";
this.labelPublishCommand.Size = new System.Drawing.Size(167, 13);
this.labelPublishCommand.TabIndex = 4;
this.labelPublishCommand.Text = "labelPublishCommand";
//
// textBoxBuildCommand
//
this.textBoxBuildCommand.Location = new System.Drawing.Point(20, 86);
this.textBoxBuildCommand.Name = "textBoxBuildCommand";
this.textBoxBuildCommand.Size = new System.Drawing.Size(368, 20);
this.textBoxBuildCommand.TabIndex = 3;
//
// labelBuildCommand
//
this.labelBuildCommand.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelBuildCommand.Location = new System.Drawing.Point(20, 52);
this.labelBuildCommand.Name = "labelBuildCommand";
this.labelBuildCommand.Size = new System.Drawing.Size(83, 13);
this.labelBuildCommand.TabIndex = 1;
this.labelBuildCommand.Text = "labelBuildCommand";
//
// textBoxPublishCommand
//
this.textBoxPublishCommand.Location = new System.Drawing.Point(20, 154);
this.textBoxPublishCommand.Name = "textBoxPublishCommand";
this.textBoxPublishCommand.Size = new System.Drawing.Size(368, 20);
this.textBoxPublishCommand.TabIndex = 6;
//
// labelSubtitle
//
this.labelSubtitle.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelSubtitle.Location = new System.Drawing.Point(20, 0);
this.labelSubtitle.Name = "labelSubtitle";
this.labelSubtitle.Size = new System.Drawing.Size(64, 13);
this.labelSubtitle.TabIndex = 0;
this.labelSubtitle.Text = "labelSubtitle";
//
// labelBuildCommandSubtitle
//
this.labelBuildCommandSubtitle.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelBuildCommandSubtitle.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.labelBuildCommandSubtitle.Location = new System.Drawing.Point(20, 70);
this.labelBuildCommandSubtitle.Name = "labelBuildCommandSubtitle";
this.labelBuildCommandSubtitle.Size = new System.Drawing.Size(83, 13);
this.labelBuildCommandSubtitle.TabIndex = 2;
this.labelBuildCommandSubtitle.Text = "labelBuildCommandSubtitle";
//
// labelPublishCommandSubtitle
//
this.labelPublishCommandSubtitle.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelPublishCommandSubtitle.Font = new System.Drawing.Font("Microsoft Sans Serif", 8F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.labelPublishCommandSubtitle.Location = new System.Drawing.Point(20, 138);
this.labelPublishCommandSubtitle.Name = "labelPublishCommandSubtitle";
this.labelPublishCommandSubtitle.Size = new System.Drawing.Size(167, 13);
this.labelPublishCommandSubtitle.TabIndex = 5;
this.labelPublishCommandSubtitle.Text = "labelPublishCommandSubtitle";
//
// WeblogConfigurationWizardPanelStaticSiteCommands
//
this.AutoSize = true;
this.Name = "WeblogConfigurationWizardPanelStaticSiteCommands";
this.Size = new System.Drawing.Size(455, 291);
this.panelMain.ResumeLayout(false);
this.panelMain.PerformLayout();
this.ResumeLayout(false);
}
#endregion
}
}

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -0,0 +1,231 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.IO;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.Extensibility.BlogClient;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.PostEditor.BlogProviderButtons;
namespace OpenLiveWriter.PostEditor.Configuration.Wizard
{
/// <summary>
/// Summary description for WelcomeToBlogControl.
/// </summary>
internal class WeblogConfigurationWizardPanelStaticSiteFeatures : WeblogConfigurationWizardPanel, IWizardPanelStaticSite
{
private CheckBox checkBoxPagesEnabled;
private CheckBox checkBoxBuildingEnabled;
private CheckBox checkBoxImagesEnabled;
private CheckBox checkBoxDraftsEnabled;
private Label labelSubtitle;
/// <summary>
/// Required designer variable.
/// </summary>
private Container components = null;
public WeblogConfigurationWizardPanelStaticSiteFeatures()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
labelHeader.Text = Res.Get(StringId.CWStaticSiteFeaturesTitle);
labelSubtitle.Text = Res.Get(StringId.CWStaticSiteFeaturesSubtitle);
checkBoxPagesEnabled.Text = Res.Get(StringId.CWStaticSiteFeaturesPages);
checkBoxImagesEnabled.Text = Res.Get(StringId.CWStaticSiteFeaturesImages);
checkBoxDraftsEnabled.Text = Res.Get(StringId.CWStaticSiteFeaturesDrafts);
checkBoxBuildingEnabled.Text = Res.Get(StringId.CWStaticSiteFeaturesBuilding);
}
public override void NaturalizeLayout()
{
// Wizard views are very broken in the VS Form Designer, due to runtime control layout.
if (DesignMode) return;
MaximizeWidth(labelSubtitle);
MaximizeWidth(checkBoxPagesEnabled);
MaximizeWidth(checkBoxDraftsEnabled);
MaximizeWidth(checkBoxImagesEnabled);
MaximizeWidth(checkBoxBuildingEnabled);
LayoutHelper.NaturalizeHeight(labelSubtitle);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3,
checkBoxPagesEnabled,
checkBoxDraftsEnabled,
checkBoxImagesEnabled,
checkBoxBuildingEnabled);
LayoutHelper.DistributeVerticallyNoScale(10, false,
labelSubtitle,
new ControlGroup(checkBoxPagesEnabled,
checkBoxDraftsEnabled,
checkBoxImagesEnabled,
checkBoxBuildingEnabled)
);
}
public override ConfigPanelId? PanelId
{
get { return ConfigPanelId.StaticSiteConfigCapabilities; }
}
public bool PagesEnabled
{
get => checkBoxPagesEnabled.Checked;
set => checkBoxPagesEnabled.Checked = value;
}
public bool DraftsEnabled
{
get => checkBoxDraftsEnabled.Checked;
set => checkBoxDraftsEnabled.Checked = value;
}
public bool ImagesEnabled
{
get => checkBoxImagesEnabled.Checked;
set => checkBoxImagesEnabled.Checked = value;
}
public bool BuildingEnabled
{
get => checkBoxBuildingEnabled.Checked;
set => checkBoxBuildingEnabled.Checked = value;
}
// No validation is required on this panel
public void ValidateWithConfig(StaticSiteConfig config) { }
/// <summary>
/// Saves panel form fields into a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void SaveToConfig(StaticSiteConfig config)
{
config.PagesEnabled = PagesEnabled;
config.DraftsEnabled = DraftsEnabled;
config.ImagesEnabled = ImagesEnabled;
config.BuildingEnabled = BuildingEnabled;
}
/// <summary>
/// Loads panel form fields from a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void LoadFromConfig(StaticSiteConfig config)
{
PagesEnabled = config.PagesEnabled;
DraftsEnabled = config.DraftsEnabled;
ImagesEnabled = config.ImagesEnabled;
BuildingEnabled = config.BuildingEnabled;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Component 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()
{
this.checkBoxPagesEnabled = new System.Windows.Forms.CheckBox();
this.labelSubtitle = new System.Windows.Forms.Label();
this.checkBoxDraftsEnabled = new System.Windows.Forms.CheckBox();
this.checkBoxImagesEnabled = new System.Windows.Forms.CheckBox();
this.checkBoxBuildingEnabled = new System.Windows.Forms.CheckBox();
this.panelMain.SuspendLayout();
this.SuspendLayout();
//
// panelMain
//
this.panelMain.Controls.Add(this.checkBoxBuildingEnabled);
this.panelMain.Controls.Add(this.checkBoxImagesEnabled);
this.panelMain.Controls.Add(this.checkBoxDraftsEnabled);
this.panelMain.Controls.Add(this.labelSubtitle);
this.panelMain.Controls.Add(this.checkBoxPagesEnabled);
this.panelMain.Size = new System.Drawing.Size(435, 242);
//
// checkBoxPagesEnabled
//
this.checkBoxPagesEnabled.Location = new System.Drawing.Point(20, 31);
this.checkBoxPagesEnabled.Name = "checkBoxPagesEnabled";
this.checkBoxPagesEnabled.Size = new System.Drawing.Size(104, 24);
this.checkBoxPagesEnabled.TabIndex = 1;
this.checkBoxPagesEnabled.Text = "checkBoxPagesEnabled";
this.checkBoxPagesEnabled.UseVisualStyleBackColor = true;
//
// labelSubtitle
//
this.labelSubtitle.Location = new System.Drawing.Point(20, 0);
this.labelSubtitle.Name = "labelSubtitle";
this.labelSubtitle.Size = new System.Drawing.Size(100, 23);
this.labelSubtitle.TabIndex = 0;
this.labelSubtitle.Text = "labelSubtitle";
this.labelSubtitle.FlatStyle = System.Windows.Forms.FlatStyle.System;
//
// checkBoxDraftsEnabled
//
this.checkBoxDraftsEnabled.Location = new System.Drawing.Point(20, 55);
this.checkBoxDraftsEnabled.Name = "checkBoxDraftsEnabled";
this.checkBoxDraftsEnabled.Size = new System.Drawing.Size(104, 24);
this.checkBoxDraftsEnabled.TabIndex = 2;
this.checkBoxDraftsEnabled.Text = "checkBoxDraftsEnabled";
this.checkBoxDraftsEnabled.UseVisualStyleBackColor = true;
//
// checkBoxImagesEnabled
//
this.checkBoxImagesEnabled.Location = new System.Drawing.Point(20, 79);
this.checkBoxImagesEnabled.Name = "checkBoxImagesEnabled";
this.checkBoxImagesEnabled.Size = new System.Drawing.Size(104, 24);
this.checkBoxImagesEnabled.TabIndex = 3;
this.checkBoxImagesEnabled.Text = "checkBoxImagesEnabled";
this.checkBoxImagesEnabled.UseVisualStyleBackColor = true;
//
// checkBoxBuildingEnabled
//
this.checkBoxBuildingEnabled.Location = new System.Drawing.Point(20, 103);
this.checkBoxBuildingEnabled.Name = "checkBoxBuildingEnabled";
this.checkBoxBuildingEnabled.Size = new System.Drawing.Size(104, 24);
this.checkBoxBuildingEnabled.TabIndex = 4;
this.checkBoxBuildingEnabled.Text = "checkBoxBuildingEnabled";
this.checkBoxBuildingEnabled.UseVisualStyleBackColor = true;
//
// WeblogConfigurationWizardPanelStaticSiteFeatures
//
this.AutoSize = true;
this.Name = "WeblogConfigurationWizardPanelStaticSiteFeatures";
this.Size = new System.Drawing.Size(455, 291);
this.panelMain.ResumeLayout(false);
this.ResumeLayout(false);
}
#endregion
}
}

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -0,0 +1,205 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.IO;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.Extensibility.BlogClient;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.PostEditor.BlogProviderButtons;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
namespace OpenLiveWriter.PostEditor.Configuration.Wizard
{
/// <summary>
/// Summary description for WelcomeToBlogControl.
/// </summary>
internal class WeblogConfigurationWizardPanelStaticSiteInitial : WeblogConfigurationWizardPanel, IWizardPanelStaticSite
{
private System.Windows.Forms.Label labelSubtitle;
private System.Windows.Forms.Label labelLocalSitePath;
private System.Windows.Forms.TextBox textBoxLocalSitePath;
private System.Windows.Forms.Button btnLocalSiteBrowse;
/// <summary>
/// Required designer variable.
/// </summary>
private Container components = null;
public WeblogConfigurationWizardPanelStaticSiteInitial()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
this.labelHeader.Text = Res.Get(StringId.CWStaticSiteInitialTitle);
this.labelSubtitle.Text = string.Format(Res.Get(StringId.CWStaticSiteInitialSubtitle), Res.Get(StringId.ProductNameVersioned));
this.labelLocalSitePath.Text = Res.Get(StringId.CWStaticSiteLocalSitePath);
}
public override void NaturalizeLayout()
{
// Wizard views are very broken in the VS Form Designer, due to runtime control layout.
if (DesignMode) return;
MaximizeWidth(labelSubtitle);
MaximizeWidth(labelLocalSitePath);
LayoutHelper.DistributeHorizontally(5, textBoxLocalSitePath, btnLocalSiteBrowse);
LayoutHelper.NaturalizeHeight(labelSubtitle);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelLocalSitePath, textBoxLocalSitePath);
// Align browse button exactly with textbox
btnLocalSiteBrowse.Height = textBoxLocalSitePath.Height;
btnLocalSiteBrowse.Top = textBoxLocalSitePath.Top;
LayoutHelper.DistributeVerticallyNoScale(20, false,
labelSubtitle,
new ControlGroup(labelLocalSitePath, textBoxLocalSitePath, btnLocalSiteBrowse)
);
}
public override ConfigPanelId? PanelId
{
get { return ConfigPanelId.StaticSiteConfigInitial; }
}
public string LocalSitePath
{
get => PathHelper.RemoveLeadingAndTrailingSlash(textBoxLocalSitePath.Text);
set { textBoxLocalSitePath.Text = value; }
}
public void ValidateWithConfig(StaticSiteConfig config)
=> config.Validator.ValidateLocalSitePath();
/// <summary>
/// Saves panel form fields into a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void SaveToConfig(StaticSiteConfig config)
{
config.LocalSitePath = LocalSitePath;
}
/// <summary>
/// Loads panel form fields from a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void LoadFromConfig(StaticSiteConfig config)
{
LocalSitePath = config.LocalSitePath;
if(config.Initialised) labelSubtitle.Text = string.Format(Res.Get(StringId.CWStaticSiteInitialSubtitleAlreadyDetected), Res.Get(StringId.ProductNameVersioned));
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Component 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(WeblogConfigurationWizardPanelStaticSiteInitial));
this.labelSubtitle = new System.Windows.Forms.Label();
this.labelLocalSitePath = new System.Windows.Forms.Label();
this.textBoxLocalSitePath = new System.Windows.Forms.TextBox();
this.btnLocalSiteBrowse = new System.Windows.Forms.Button();
this.panelMain.SuspendLayout();
this.SuspendLayout();
//
// panelMain
//
this.panelMain.Controls.Add(this.labelSubtitle);
this.panelMain.Controls.Add(this.labelLocalSitePath);
this.panelMain.Controls.Add(this.textBoxLocalSitePath);
this.panelMain.Controls.Add(this.btnLocalSiteBrowse);
this.panelMain.Size = new System.Drawing.Size(435, 215);
//
// labelSubtitle
//
this.labelSubtitle.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelSubtitle.Location = new System.Drawing.Point(20, 0);
this.labelSubtitle.Margin = new System.Windows.Forms.Padding(0);
this.labelSubtitle.Name = "labelSubtitle";
this.labelSubtitle.Size = new System.Drawing.Size(415, 75);
this.labelSubtitle.TabIndex = 0;
//
// labelLocalSitePath
//
this.labelLocalSitePath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelLocalSitePath.Location = new System.Drawing.Point(20, 75);
this.labelLocalSitePath.Name = "labelLocalSitePath";
this.labelLocalSitePath.Size = new System.Drawing.Size(167, 13);
this.labelLocalSitePath.TabIndex = 1;
this.labelLocalSitePath.Text = "Path to local site:";
//
// textBoxLocalSitePath
//
this.textBoxLocalSitePath.Location = new System.Drawing.Point(20, 91);
this.textBoxLocalSitePath.Name = "textBoxLocalSitePath";
this.textBoxLocalSitePath.Size = new System.Drawing.Size(275, 20);
this.textBoxLocalSitePath.TabIndex = 2;
//
// btnLocalSiteBrowse
//
this.btnLocalSiteBrowse.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.btnLocalSiteBrowse.Location = new System.Drawing.Point(298, 91);
this.btnLocalSiteBrowse.Margin = new System.Windows.Forms.Padding(0);
this.btnLocalSiteBrowse.Name = "btnLocalSiteBrowse";
this.btnLocalSiteBrowse.Size = new System.Drawing.Size(20, 23);
this.btnLocalSiteBrowse.TabIndex = 3;
this.btnLocalSiteBrowse.Text = "...";
this.btnLocalSiteBrowse.Click += new System.EventHandler(this.BtnLocalSiteBrowse_Click);
//
// WeblogConfigurationWizardPanelStaticSiteInitial
//
this.AutoSize = true;
this.Name = "WeblogConfigurationWizardPanelStaticSiteInitial";
this.Size = new System.Drawing.Size(455, 264);
this.panelMain.ResumeLayout(false);
this.panelMain.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private void BtnLocalSiteBrowse_Click(object sender, EventArgs args)
{
var folderBrowserDialog = new FolderBrowserDialog();
folderBrowserDialog.ShowNewFolderButton = false;
folderBrowserDialog.Description = Res.Get(StringId.CWStaticSiteLocalSiteFolderPicker);
var result = folderBrowserDialog.ShowDialog();
if (result == DialogResult.OK)
{
textBoxLocalSitePath.Text = folderBrowserDialog.SelectedPath;
}
}
}
}

View File

@ -0,0 +1,123 @@
<?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>
<data name="labelSubtitle.Text" xml:space="preserve">
<value>Open Live Writer will attempt to automatically detect your static site configuration based on files present in your site project folder. Please select the project folder of your static site (eg. Git repository)</value>
</data>
</root>

View File

@ -0,0 +1,337 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.IO;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.Extensibility.BlogClient;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.PostEditor.BlogProviderButtons;
namespace OpenLiveWriter.PostEditor.Configuration.Wizard
{
/// <summary>
/// Summary description for WelcomeToBlogControl.
/// </summary>
internal class WeblogConfigurationWizardPanelStaticSitePaths1 : WeblogConfigurationWizardPanel, IWizardPanelStaticSite
{
/// <summary>
/// Local site path, loaded from config, used for validation
/// </summary>
private string _localSitePath;
private Label labelPostsPath;
private TextBox textBoxPostsPath;
private TextBox textBoxPagesPath;
private Label labelSiteUrl;
private TextBox textBoxSiteUrl;
private TextBox textBoxDraftsPath;
private CheckBox checkBoxPagesInRoot;
private Label labelDraftsPath;
private Label labelPagesPath;
/// <summary>
/// Required designer variable.
/// </summary>
private Container components = null;
public WeblogConfigurationWizardPanelStaticSitePaths1()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
labelHeader.Text = Res.Get(StringId.CWStaticSitePathsTitle);
labelSiteUrl.Text = Res.Get(StringId.CWStaticSitePathsSiteUrl);
labelPostsPath.Text = Res.Get(StringId.CWStaticSitePathsPostsPath);
labelPagesPath.Text = Res.Get(StringId.CWStaticSitePathsPagesPath);
labelDraftsPath.Text = Res.Get(StringId.CWStaticSitePathsDraftsPath);
checkBoxPagesInRoot.Text = Res.Get(StringId.CWStaticSitePathsPagesInRoot);
}
public override void NaturalizeLayout()
{
// Wizard views are very broken in the VS Form Designer, due to runtime control layout.
if (DesignMode) return;
MaximizeWidth(textBoxSiteUrl);
MaximizeWidth(labelPostsPath);
MaximizeWidth(textBoxPostsPath);
MaximizeWidth(labelPagesPath);
MaximizeWidth(textBoxPagesPath);
MaximizeWidth(checkBoxPagesInRoot);
MaximizeWidth(labelDraftsPath);
MaximizeWidth(textBoxDraftsPath);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelSiteUrl, textBoxSiteUrl);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelPostsPath, textBoxPostsPath);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelPagesPath, textBoxPagesPath, checkBoxPagesInRoot);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelDraftsPath, textBoxDraftsPath);
LayoutHelper.DistributeVerticallyNoScale(10, false,
new ControlGroup(labelSiteUrl, textBoxSiteUrl),
new ControlGroup(labelPostsPath, textBoxPostsPath),
new ControlGroup(labelPagesPath, textBoxPagesPath, checkBoxPagesInRoot),
new ControlGroup(labelDraftsPath, textBoxDraftsPath)
);
}
public override ConfigPanelId? PanelId
{
get { return ConfigPanelId.StaticSiteConfigPaths1; }
}
public string SiteUrl
{
get => PathHelper.RemoveLeadingAndTrailingSlash(textBoxSiteUrl.Text);
set { textBoxSiteUrl.Text = value; }
}
public string PostsPath
{
get => PathHelper.RemoveLeadingAndTrailingSlash(textBoxPostsPath.Text);
set { textBoxPostsPath.Text = value; }
}
private bool _pagesEnabled = false;
public bool PagesEnabled
{
get => _pagesEnabled;
set
{
_pagesEnabled
= labelPagesPath.Enabled
= value;
RecomputeEnabledStates();
}
}
public string PagesPath
{
get => PathHelper.RemoveLeadingAndTrailingSlash(textBoxPagesPath.Text);
set { textBoxPagesPath.Text = value; }
}
private bool _draftsEnabled = false;
public bool DraftsEnabled
{
get => _draftsEnabled;
set => _draftsEnabled
= labelDraftsPath.Enabled
= textBoxDraftsPath.Enabled
= value;
}
public string DraftsPath
{
get => PathHelper.RemoveLeadingAndTrailingSlash(textBoxDraftsPath.Text);
set { textBoxDraftsPath.Text = value; }
}
public bool PagesInRoot
{
get => checkBoxPagesInRoot.Checked;
set { checkBoxPagesInRoot.Checked = value; }
}
public void ValidateWithConfig(StaticSiteConfig config)
=> config.Validator
.ValidatePostsPath()
.ValidatePagesPath()
.ValidateDraftsPath();
/// <summary>
/// Saves panel form fields into a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void SaveToConfig(StaticSiteConfig config)
{
config.SiteUrl = SiteUrl;
config.PostsPath = PostsPath;
config.PagesPath = PagesInRoot ? "." : PagesPath;
config.DraftsPath = DraftsPath;
}
/// <summary>
/// Loads panel form fields from a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void LoadFromConfig(StaticSiteConfig config)
{
_localSitePath = config.LocalSitePath;
SiteUrl = config.SiteUrl;
PostsPath = config.PostsPath;
PagesEnabled = config.PagesEnabled;
PagesInRoot = config.PagesPath == ".";
PagesPath = config.PagesPath;
DraftsEnabled = config.DraftsEnabled;
DraftsPath = config.DraftsPath;
RecomputeEnabledStates();
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Component 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()
{
this.labelPostsPath = new System.Windows.Forms.Label();
this.textBoxPostsPath = new System.Windows.Forms.TextBox();
this.textBoxPagesPath = new System.Windows.Forms.TextBox();
this.labelSiteUrl = new System.Windows.Forms.Label();
this.textBoxSiteUrl = new System.Windows.Forms.TextBox();
this.textBoxDraftsPath = new System.Windows.Forms.TextBox();
this.checkBoxPagesInRoot = new System.Windows.Forms.CheckBox();
this.labelPagesPath = new System.Windows.Forms.Label();
this.labelDraftsPath = new System.Windows.Forms.Label();
this.panelMain.SuspendLayout();
this.SuspendLayout();
//
// panelMain
//
this.panelMain.Controls.Add(this.labelDraftsPath);
this.panelMain.Controls.Add(this.labelPagesPath);
this.panelMain.Controls.Add(this.checkBoxPagesInRoot);
this.panelMain.Controls.Add(this.textBoxDraftsPath);
this.panelMain.Controls.Add(this.textBoxSiteUrl);
this.panelMain.Controls.Add(this.labelSiteUrl);
this.panelMain.Controls.Add(this.labelPostsPath);
this.panelMain.Controls.Add(this.textBoxPostsPath);
this.panelMain.Controls.Add(this.textBoxPagesPath);
this.panelMain.Size = new System.Drawing.Size(435, 242);
//
// labelPostsPath
//
this.labelPostsPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelPostsPath.Location = new System.Drawing.Point(20, 45);
this.labelPostsPath.Name = "labelPostsPath";
this.labelPostsPath.Size = new System.Drawing.Size(167, 13);
this.labelPostsPath.TabIndex = 2;
this.labelPostsPath.Text = "labelPostsPath";
//
// textBoxPostsPath
//
this.textBoxPostsPath.Location = new System.Drawing.Point(20, 61);
this.textBoxPostsPath.Name = "textBoxPostsPath";
this.textBoxPostsPath.Size = new System.Drawing.Size(368, 20);
this.textBoxPostsPath.TabIndex = 3;
//
// textBoxPagesPath
//
this.textBoxPagesPath.Enabled = false;
this.textBoxPagesPath.Location = new System.Drawing.Point(20, 113);
this.textBoxPagesPath.Name = "textBoxPagesPath";
this.textBoxPagesPath.Size = new System.Drawing.Size(368, 20);
this.textBoxPagesPath.TabIndex = 5;
//
// labelSiteUrl
//
this.labelSiteUrl.AutoSize = true;
this.labelSiteUrl.Location = new System.Drawing.Point(17, 0);
this.labelSiteUrl.Name = "labelSiteUrl";
this.labelSiteUrl.Size = new System.Drawing.Size(60, 13);
this.labelSiteUrl.TabIndex = 0;
this.labelSiteUrl.Text = "labelSiteUrl";
//
// textBoxSiteUrl
//
this.textBoxSiteUrl.Location = new System.Drawing.Point(20, 17);
this.textBoxSiteUrl.Name = "textBoxSiteUrl";
this.textBoxSiteUrl.Size = new System.Drawing.Size(368, 20);
this.textBoxSiteUrl.TabIndex = 1;
//
// textBoxDraftsPath
//
this.textBoxDraftsPath.Enabled = false;
this.textBoxDraftsPath.Location = new System.Drawing.Point(20, 193);
this.textBoxDraftsPath.Name = "textBoxDraftsPath";
this.textBoxDraftsPath.Size = new System.Drawing.Size(368, 20);
this.textBoxDraftsPath.TabIndex = 8;
//
// checkBoxPagesInRoot
//
this.checkBoxPagesInRoot.AutoSize = true;
this.checkBoxPagesInRoot.Enabled = false;
this.checkBoxPagesInRoot.Location = new System.Drawing.Point(20, 139);
this.checkBoxPagesInRoot.Name = "checkBoxPagesInRoot";
this.checkBoxPagesInRoot.RightToLeft = System.Windows.Forms.RightToLeft.No;
this.checkBoxPagesInRoot.Size = new System.Drawing.Size(136, 17);
this.checkBoxPagesInRoot.TabIndex = 6;
this.checkBoxPagesInRoot.Text = "checkBoxPagesInRoot";
this.checkBoxPagesInRoot.UseVisualStyleBackColor = true;
this.checkBoxPagesInRoot.CheckedChanged += new System.EventHandler(this.CheckBoxPagesInRoot_CheckedChanged);
//
// labelPagesPath
//
this.labelPagesPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelPagesPath.Location = new System.Drawing.Point(20, 97);
this.labelPagesPath.Name = "labelPagesPath";
this.labelPagesPath.Size = new System.Drawing.Size(167, 13);
this.labelPagesPath.TabIndex = 9;
this.labelPagesPath.Text = "labelPagesPath";
//
// labelDraftsPath
//
this.labelDraftsPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelDraftsPath.Location = new System.Drawing.Point(20, 177);
this.labelDraftsPath.Name = "labelDraftsPath";
this.labelDraftsPath.Size = new System.Drawing.Size(167, 13);
this.labelDraftsPath.TabIndex = 10;
this.labelDraftsPath.Text = "labelDraftsPath";
//
// WeblogConfigurationWizardPanelStaticSitePaths1
//
this.AutoSize = true;
this.Name = "WeblogConfigurationWizardPanelStaticSitePaths1";
this.Size = new System.Drawing.Size(455, 291);
this.panelMain.ResumeLayout(false);
this.panelMain.PerformLayout();
this.ResumeLayout(false);
}
#endregion
private void CheckBoxPagesInRoot_CheckedChanged(object sender, EventArgs e)
{
RecomputeEnabledStates();
if (PagesInRoot) PagesPath = ".";
}
private void RecomputeEnabledStates()
{
checkBoxPagesInRoot.Enabled = PagesEnabled;
textBoxPagesPath.Enabled = PagesEnabled && !PagesInRoot;
}
}
}

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -0,0 +1,209 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.IO;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using OpenLiveWriter.ApplicationFramework.Preferences;
using OpenLiveWriter.BlogClient;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
using OpenLiveWriter.CoreServices;
using OpenLiveWriter.CoreServices.Layout;
using OpenLiveWriter.Extensibility.BlogClient;
using OpenLiveWriter.Localization;
using OpenLiveWriter.Localization.Bidi;
using OpenLiveWriter.PostEditor.BlogProviderButtons;
namespace OpenLiveWriter.PostEditor.Configuration.Wizard
{
/// <summary>
/// Summary description for WelcomeToBlogControl.
/// </summary>
internal class WeblogConfigurationWizardPanelStaticSitePaths2 : WeblogConfigurationWizardPanel, IWizardPanelStaticSite
{
private Label labelImagesPath;
private TextBox textBoxImagesPath;
private Label labelOutputPath;
private TextBox textBoxOutputPath;
/// <summary>
/// Required designer variable.
/// </summary>
private Container components = null;
public WeblogConfigurationWizardPanelStaticSitePaths2()
{
// This call is required by the Windows.Forms Form Designer.
InitializeComponent();
this.labelHeader.Text = Res.Get(StringId.CWStaticSitePathsTitle);
this.labelImagesPath.Text = Res.Get(StringId.CWStaticSitePathsImagesPath);
this.labelOutputPath.Text = Res.Get(StringId.CWStaticSitePathsOutputPath);
}
public override void NaturalizeLayout()
{
// Wizard views are very broken in the VS Form Designer, due to runtime control layout.
if (DesignMode) return;
MaximizeWidth(labelImagesPath);
MaximizeWidth(textBoxImagesPath);
MaximizeWidth(labelOutputPath);
MaximizeWidth(textBoxOutputPath);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelImagesPath, textBoxImagesPath);
LayoutHelper.NaturalizeHeightAndDistributeNoScale(3, labelOutputPath, textBoxOutputPath);
LayoutHelper.DistributeVerticallyNoScale(10, false,
new ControlGroup(labelImagesPath, textBoxImagesPath),
new ControlGroup(labelOutputPath, textBoxOutputPath)
);
}
public override ConfigPanelId? PanelId
{
get { return ConfigPanelId.StaticSiteConfigPaths2; }
}
private bool _imagesEnabled = false;
public bool ImagesEnabled
{
get => _imagesEnabled;
set => _imagesEnabled = labelImagesPath.Enabled = textBoxImagesPath.Enabled = value;
}
public string ImagesPath
{
get => PathHelper.RemoveLeadingAndTrailingSlash(textBoxImagesPath.Text);
set => textBoxImagesPath.Text = value;
}
private bool _buildingEnabled = false;
public bool BuildingEnabled
{
get => _buildingEnabled;
set => _buildingEnabled = labelOutputPath.Enabled = textBoxOutputPath.Enabled = value;
}
public string OutputPath
{
get => PathHelper.RemoveLeadingAndTrailingSlash(textBoxOutputPath.Text);
set => textBoxOutputPath.Text = value;
}
public void ValidateWithConfig(StaticSiteConfig config)
=> config.Validator
.ValidateImagesPath()
.ValidateOutputPath();
/// <summary>
/// Saves panel form fields into a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void SaveToConfig(StaticSiteConfig config)
{
config.ImagesPath = ImagesPath;
config.OutputPath = OutputPath;
}
/// <summary>
/// Loads panel form fields from a StaticSiteConfig
/// </summary>
/// <param name="config">a StaticSiteConfig instance</param>
public void LoadFromConfig(StaticSiteConfig config)
{
ImagesEnabled = config.ImagesEnabled;
ImagesPath = config.ImagesPath;
BuildingEnabled = config.BuildingEnabled;
OutputPath = config.OutputPath;
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose(disposing);
}
#region Component 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()
{
this.labelImagesPath = new System.Windows.Forms.Label();
this.textBoxImagesPath = new System.Windows.Forms.TextBox();
this.labelOutputPath = new System.Windows.Forms.Label();
this.textBoxOutputPath = new System.Windows.Forms.TextBox();
this.panelMain.SuspendLayout();
this.SuspendLayout();
//
// panelMain
//
this.panelMain.Controls.Add(this.labelImagesPath);
this.panelMain.Controls.Add(this.textBoxImagesPath);
this.panelMain.Controls.Add(this.labelOutputPath);
this.panelMain.Controls.Add(this.textBoxOutputPath);
this.panelMain.Size = new System.Drawing.Size(435, 242);
//
// labelImagesPath
//
this.labelImagesPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelImagesPath.Location = new System.Drawing.Point(20, 0);
this.labelImagesPath.Name = "labelImagesPath";
this.labelImagesPath.Size = new System.Drawing.Size(167, 13);
this.labelImagesPath.TabIndex = 0;
this.labelImagesPath.Text = "Images path: (relative)";
//
// textBoxImagesPath
//
this.textBoxImagesPath.Location = new System.Drawing.Point(20, 61);
this.textBoxImagesPath.Name = "textBoxImagesPath";
this.textBoxImagesPath.Size = new System.Drawing.Size(368, 20);
this.textBoxImagesPath.TabIndex = 1;
//
// labelOutputPath
//
this.labelOutputPath.Enabled = false;
this.labelOutputPath.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.labelOutputPath.Location = new System.Drawing.Point(20, 40);
this.labelOutputPath.Name = "labelOutputPath";
this.labelOutputPath.Size = new System.Drawing.Size(83, 13);
this.labelOutputPath.TabIndex = 2;
this.labelOutputPath.Text = "Build output path: (relative)";
//
// textBoxOutputPath
//
this.textBoxOutputPath.Enabled = false;
this.textBoxOutputPath.Location = new System.Drawing.Point(20, 17);
this.textBoxOutputPath.Name = "textBoxOutputPath";
this.textBoxOutputPath.Size = new System.Drawing.Size(368, 20);
this.textBoxOutputPath.TabIndex = 3;
//
// WeblogConfigurationWizardPanelStaticSitePaths2
//
this.AutoSize = true;
this.Name = "WeblogConfigurationWizardPanelStaticSitePaths2";
this.Size = new System.Drawing.Size(455, 291);
this.panelMain.ResumeLayout(false);
this.panelMain.PerformLayout();
this.ResumeLayout(false);
}
#endregion
}
}

View File

@ -0,0 +1,120 @@
<?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>
</root>

View File

@ -289,6 +289,25 @@
<Compile Include="Configuration\Settings\WeblogSettingsPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\StaticSiteAdvanced\FrontMatterPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\StaticSiteAdvanced\AuthoringPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\StaticSiteAdvanced\BuildPublishPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\StaticSiteAdvanced\GeneralPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\StaticSiteAdvanced\StaticSitePreferencesController.cs" />
<Compile Include="Configuration\StaticSiteAdvanced\StaticSitePreferencesForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Configuration\StaticSiteAdvanced\StaticSitePreferencesPanel.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\TemporaryBlogSettings.cs" />
<Compile Include="Configuration\Wizard\BlogProviderAccountWizard.cs" />
<Compile Include="Configuration\Wizard\WeblogConfigurationWizard.cs">
@ -304,6 +323,21 @@
<Compile Include="Configuration\Wizard\WeblogConfigurationWizardPanelAutoDetection.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSiteFeatures.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSiteCommands.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSitePaths2.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSitePaths1.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSiteInitial.cs">
<SubType>UserControl</SubType>
</Compile>
<Compile Include="Configuration\Wizard\WeblogConfigurationWizardPanelBasicInfo.cs">
<SubType>UserControl</SubType>
</Compile>
@ -464,6 +498,11 @@
</Compile>
<Compile Include="ContentSources\IPublishTimeWorker.cs" />
<Compile Include="ContentSources\SmartContentWorker.cs" />
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<Compile Include="SmartContentPersistedFileList.cs" />
<Compile Include="PingPreferencesPanel.cs">
<SubType>UserControl</SubType>
@ -1018,6 +1057,22 @@
<DependentUpon>WeblogSettingsPanel.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\StaticSiteAdvanced\FrontMatterPanel.resx">
<DependentUpon>FrontMatterPanel.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\StaticSiteAdvanced\AuthoringPanel.resx">
<DependentUpon>AuthoringPanel.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\StaticSiteAdvanced\BuildPublishPanel.resx">
<DependentUpon>BuildPublishPanel.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\StaticSiteAdvanced\GeneralPanel.resx">
<DependentUpon>GeneralPanel.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\Wizard\Images\LiveID16.gif" />
<EmbeddedResource Include="Configuration\Wizard\Images\WeblogWizardHeader.png" />
<EmbeddedResource Include="Configuration\Wizard\WeblogConfigurationWizard.resx">
@ -1036,6 +1091,26 @@
<DependentUpon>WeblogConfigurationWizardPanelAutoDetection.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSiteFeatures.resx">
<DependentUpon>WeblogConfigurationWizardPanelStaticSiteFeatures.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSiteCommands.resx">
<DependentUpon>WeblogConfigurationWizardPanelStaticSiteCommands.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSitePaths2.resx">
<DependentUpon>WeblogConfigurationWizardPanelStaticSitePaths2.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSitePaths1.resx">
<DependentUpon>WeblogConfigurationWizardPanelStaticSitePaths1.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\Wizard\WeblogConfigurationWizardPanelStaticSiteInitial.resx">
<DependentUpon>WeblogConfigurationWizardPanelStaticSiteInitial.cs</DependentUpon>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="Configuration\Wizard\WeblogConfigurationWizardPanelBasicInfo.resx">
<DependentUpon>WeblogConfigurationWizardPanelBasicInfo.cs</DependentUpon>
<SubType>Designer</SubType>
@ -1447,6 +1522,10 @@
<SubType>Designer</SubType>
<DependentUpon>PrivacyPreferencesPanel.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="RecentPostNotSynchronizedWarningForm.resx">
<DependentUpon>RecentPostNotSynchronizedWarningForm.cs</DependentUpon>
<SubType>Designer</SubType>

View File

@ -0,0 +1,63 @@
//------------------------------------------------------------------------------
// <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 OpenLiveWriter.PostEditor.Properties {
using System;
/// <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", "16.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 (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("OpenLiveWriter.PostEditor.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,120 @@
<?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>
</root>

View File

@ -0,0 +1,108 @@
using System.Linq;
using NUnit.Framework;
using OpenLiveWriter.BlogClient.Clients.StaticSite;
namespace OpenLiveWriter.Tests.BlogClient.Clients.StaticSite
{
[TestFixture]
class StaticSiteItemFrontMatterTests
{
[Test]
public void Deserialize_Basic()
{
// Expected
var expected = new StaticSiteItemFrontMatter(new StaticSiteConfigFrontMatterKeys())
{
Title = "Test title",
Date = "2019-01-01 00:00:00",
Layout = "post",
Tags = new string[] { "programming", ".net" }
};
// Act
var fm = new StaticSiteItemFrontMatter(new StaticSiteConfigFrontMatterKeys());
fm.Deserialize(@"title: Test title
date: 2019-01-01 00:00:00
layout: post
tags:
- programming
- .net");
// Assert
Assert.AreEqual(fm.Title, expected.Title);
Assert.AreEqual(fm.Date, expected.Date);
Assert.AreEqual(fm.Layout, expected.Layout);
Assert.IsTrue(Enumerable.SequenceEqual(fm.Tags, expected.Tags));
}
[Test]
public void Deserialize_MissingKeys()
{
// Expected
var expected = new StaticSiteItemFrontMatter(new StaticSiteConfigFrontMatterKeys())
{
Title = "Test title",
Date = "2019-01-01 00:00:00",
Tags = new string[] {}
};
// Act
var fm = new StaticSiteItemFrontMatter(new StaticSiteConfigFrontMatterKeys());
fm.Deserialize(@"title: Test title
date: 2019-01-01 00:00:00");
// Assert
Assert.AreEqual(fm.Title, expected.Title);
Assert.AreEqual(fm.Date, expected.Date);
Assert.AreEqual(fm.Layout, expected.Layout);
Assert.IsTrue(Enumerable.SequenceEqual(fm.Tags, expected.Tags));
}
[Test]
public void Serialize_Basic()
{
// Expected
var expected = @"title: Test title
date: 2019-01-01 00:00:00
layout: post
";
// Act
var fm = new StaticSiteItemFrontMatter(new StaticSiteConfigFrontMatterKeys())
{
Title = "Test title",
Date = "2019-01-01 00:00:00",
Layout = "post"
};
// Assert
Assert.AreEqual(expected, fm.Serialize());
}
[Test]
public void Serialize_WithTags()
{
// Expected
var expected = @"title: Test title
date: 2019-01-01 00:00:00
layout: post
tags:
- hello
- world
";
// Act
var fm = new StaticSiteItemFrontMatter(new StaticSiteConfigFrontMatterKeys())
{
Title = "Test title",
Date = "2019-01-01 00:00:00",
Layout = "post",
Tags = new string[] {"hello", "world"}
};
// Assert
Assert.AreEqual(expected, fm.Serialize());
}
}
}

View File

@ -124,6 +124,7 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="BlogClient\Clients\StaticSite\StaticSiteItemFrontMatterTests.cs" />
<Compile Include="PostEditor\Tables\PixelPercentTests.cs" />
<Compile Include="PostEditor\Tables\TestHtmlEditor.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
@ -138,6 +139,10 @@
<Project>{eeee653a-90ce-44e2-b40e-22f747880cc0}</Project>
<Name>OpenLiveWriter.ApplicationFramework</Name>
</ProjectReference>
<ProjectReference Include="..\OpenLiveWriter.BlogClient\OpenLiveWriter.BlogClient.csproj">
<Project>{fe2ea529-26e6-42c3-9f6a-e58966995ffe}</Project>
<Name>OpenLiveWriter.BlogClient</Name>
</ProjectReference>
<ProjectReference Include="..\OpenLiveWriter.HtmlEditor\OpenLiveWriter.HtmlEditor.csproj">
<Project>{6A6872BC-67EF-4A42-A21A-30ECED376923}</Project>
<Name>OpenLiveWriter.HtmlEditor</Name>

View File

@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.572
# Visual Studio Version 16
VisualStudioVersion = 16.0.29001.49
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenLiveWriter.CoreServices", "OpenLiveWriter.CoreServices\OpenLiveWriter.CoreServices.csproj", "{9154B6B4-F2C3-4FB4-BE38-A26A6C9409EE}"
EndProject
@ -88,6 +88,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenLiveWriter.SpellChecker
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenLiveWriter.Tests", "OpenLiveWriter.Tests\OpenLiveWriter.Tests.csproj", "{B3E5F002-AFD0-427A-BCBB-B1E4FB6FC92E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LocEdit", "LocEdit\LocEdit.csproj", "{3B04E986-B74C-4151-8327-57F6BA8DECBC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -263,6 +265,12 @@ Global
{B3E5F002-AFD0-427A-BCBB-B1E4FB6FC92E}.Release|Any CPU.Build.0 = Release|Any CPU
{B3E5F002-AFD0-427A-BCBB-B1E4FB6FC92E}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU
{B3E5F002-AFD0-427A-BCBB-B1E4FB6FC92E}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU
{3B04E986-B74C-4151-8327-57F6BA8DECBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3B04E986-B74C-4151-8327-57F6BA8DECBC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3B04E986-B74C-4151-8327-57F6BA8DECBC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3B04E986-B74C-4151-8327-57F6BA8DECBC}.Release|Any CPU.Build.0 = Release|Any CPU
{3B04E986-B74C-4151-8327-57F6BA8DECBC}.ReleaseSigned|Any CPU.ActiveCfg = Release|Any CPU
{3B04E986-B74C-4151-8327-57F6BA8DECBC}.ReleaseSigned|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE