Un plugin per Windows Live Writer
Nel poco tempo a disposizione nell'ultima settimana (una motherboard si è rotta e la nuova si è rivelata difettosa giusto alla fine delle reinstallazioni e ripristino dati) ho provato ad arricchire questo blog con alcuni accessori, come snapshot per i link. Dopo essermi registrato su Technorati, inoltre, ho aggiunto ai post già scritti (tanto sono ancora pochi) i Technorati tags e ho voluto provare ad aggiungere a ciascuno di essi il Link Count Widget, ossia un'indicazione di quanti sono i blog che linkano quel post (per la cronaca, ancora nessuno fino ad ora
).
Per aggiungere tale feature su Community Server bisogna inserire in ogni post questo snippet in HTML:
<script src="http://embed.technorati.com/linkcount" type="text/javascript">
</script>
<a class="tr-linkcount" href="http://technorati.com/search/{URL}">
View blog reactions</a>
In realtà la prima parte può essere messa una volta per tutte nell'header HTML delle pagine del blog, specificandolo nella Dashboard relativa al proprio account. Nella seconda parte, invece, bisogna sostituire a {URL} l'indirizzo completo del post in questione.
Il problema è che l'indirizzo del post non è noto fino a che il post non viene pubblicato, anche se è ricavato dal titolo dello stesso e dalla data di pubblicazione con un semplice algoritmo. Un metodo semplice per ovviare a questo inconveniente sarebbe di scrivere al posto di {URL} un'espressione di DataBind del tipo:
<%# BlogUrls.Instance().Post(WeblogControlUtility.Instance()
.GetCurrentWeblogPost(this)) %>
Purtroppo, per ovvi motivi di sicurezza, non è possibile inserire espressioni di DataBind in un post (sarebbero codificate e rese innocue). Pertanto, non avendo accesso alle impostazioni di administrator della piattaforma, l'unica scelta sembrava quella di una doppia pubblicazione, con l'inserzione manuale dello snippet e dell'URL solo in seconda battuta.
Una leggera semplificazione, però, si può ottenere grazie ad un semplice plugin per Live Writer, che sia in grado di automatizzare parte della procedura ed evitare di dover inviare due volte il post (suggerimento di Paperino).
Per farlo da qui ho scaricato da qui Windows Live Writer SDK, l'ho installato ed ho dato un'occhiata alla documentazione ed al plugin di esempio (HelloWorldPlugin), da cui ho capito che per risolvere il mio problema avrei dovuto scrivere un Simple Content Source Plugin. Come source per il content avrei dovuto un Insert Dialog.
I passi per la creazione di un Content Source Plugin dalle caratteristiche indicate sono i seguenti:
- creare un nuovo progetto Class Library in Visual Studio;
- aggiungere una reference all'assembly WindowsLive.Writer.Api (che si trova nella stessa cartella dell'eseguibile di Windows Live Writer, ad es: C:\Programmi\Windows Live Writer);
- creare una nuova classe, nel mio caso TLCWPlugin, derivata dalla classe ContentSource;
- aggiungere a questa classe gli attributi WriterPluginAttribute e InsertableContentSourceAttribute e scrivere l'override del metodo CreateContent.
Se si aggiunge alle opzioni di compilazione la nei post-build event command line
XCOPY /D /Y /R "$(TargetPath)" "C:\Programmi\Windows Live Writer\Plugins\"
allora la dll generata sarà copiata direttamente nella directory dei plugin di Live Writer per essere provata.
Il codice risultante è (in VB.NET):
Imports System
Imports System.Windows.Forms
Imports WindowsLive.Writer.Api
<WriterPlugin("ce9a848e-c786-4e54-a0e2-1abff354e396",
"Technorati Link Count Widget", ImagePath:="Technorati.png", _
Description:="Insert Technorati Link Count Widget in your post.", _
PublisherUrl:="http://www.webis.it")> _
<InsertableContentSource("Technorati Link Count Widget", _
SidebarText:="Link Count Widget")> _
Public Class TLCWPlugin
Inherits ContentSource
Public Overrides Function CreateContent( _
ByVal dialogOwner As System.Windows.Forms.IWin32Window, _
ByRef newContent As String) As System.Windows.Forms.DialogResult
Using InsertForm As TLCWInsertForm = New TLCWInsertForm
Dim risultato As DialogResult = InsertForm.ShowDialog
If risultato = DialogResult.OK Then
newContent = InsertForm.Stringa
End If
Return risultato
End Using
End Function
End Class
Come si può vedere nell'attributo WriterPluginAttribute, oltre a id e nome, è possibile specificare anche altre informazioni, quali l'icona del plugin (una immagine embedded di dimensioni 20x18). Nell'attributo InsertableContentSource, inoltre, si può specificare un testo per la Sidebar diverso da quello del menù (c'è meno spazio).
Nel corpo del metodo CreateContent, invece, il codice non fa altro che aprire una finestra di dialogo con la form TLCWInsertForm, in cui sarà fatto tutto il lavoro.
Se la finestra è chiusa con DialogResult.OK (Insert), allora verrà inserito il nuovo contenuto, prelevandolo dalla proprietà pubblica Stringa della classe TLCWInsertForm.
Il codice per la proprietà è semplicemente:
Private _Stringa As String
Public Property Stringa() As String
Get
Return _Stringa
End Get
Set(ByVal Value As String)
_Stringa = Value
End Set
End Property
(scritto ancora più velocemente grazie a questa macro di Francesco Balena, leggermente modificata, che trasforma field in proprietà)
Al caricamento della form viene eseguito il codice seguente, che carica (se presenti) le impostazioni memorizzate per il plugin, ossia l'URL del blog[1] e la presenza nell'header HTML del tag script:
Private Sub TLCWInsertForm_Load(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Load
Me.DateTimePicker1.Value = Date.Today
Me.Stringa = ""
Me.LoadSettings()
Me.TextBoxBlogUrl.Text = Me._blogUrl
Me.CheckBoxJsInHeader.Checked = Me._checked
Me.LabelNote.Visible = Not Me.CheckBoxJsInHeader.Checked
End Sub
Private Sub LoadSettings()
Dim fs As FileStream = Nothing
Dim rd As StreamReader
Me._blogUrl = Me._defaultBlogUrl
Me._checked = Me._defaultChecked
Try
fs = New FileStream(Me._configPath & Path.DirectorySeparatorChar & _
Me._configFileName, FileMode.Open)
rd = New StreamReader(fs)
Me._blogUrl = rd.ReadLine().Trim
If CBool(rd.ReadLine) = True Then
Me._checked = True
End If
Catch ex As Exception
Finally
If fs IsNot Nothing Then
fs.Close()
End If
End Try
End Sub
Invece alla pressione di Insert, vengono effettuati controlli sulla presenza dei campi obbligatori, viene calcolato l'url in base alla data (di default è impostata quella di sistema) ed al titolo (attraverso la funzione UrlEncode), si imposta la proprietà Stringa e vengono salvate le impostazioni se sono cambiate.
Private Sub ButtonOK_Click(ByVal sender As Object, ByVal e As System.EventArgs) _
Handles ButtonOK.Click
If DatiValidi() Then
Dim url As String
url = TextBoxBlogUrl.Text
If Not url.EndsWith("/") Then
url &= "/"
End If
url &= DateTimePicker1.Value.Year.ToString & "/" & _
DateTimePicker1.Value.Month.ToString("00") & "/" & _
DateTimePicker1.Value.Day.ToString("00") & "/" & _
UrlEncode(TextBoxPostTitle.Text, _
New Regex("([^A-Za-z0-9 ]+|\.| )", _
(RegexOptions.Singleline Or RegexOptions.Compiled)), "-"c, "_"c) & _
".aspx"
Me.Stringa = String.Format(Me._technoratiString, url)
If CheckBoxJsInHeader.Checked = False Then
Me.Stringa = Me._technoratiJavascript & Me.Stringa
End If
If Not Me.SaveSettings() Then
MsgBox("Error while saving your settings.")
End If
Else
Me.DialogResult = Windows.Forms.DialogResult.None
End If
End Sub
Private Function SaveSettings()
Dim risultato As Boolean = True
' salva solo se le impostazioni sono cambiate
If (Me._blogUrl <> TextBoxBlogUrl.Text) OrElse _
(Me._checked <> CheckBoxJsInHeader.Checked) Then
Dim wr As StreamWriter = Nothing
Try
If Not Directory.Exists(Me._configPath) Then
Directory.CreateDirectory(Me._configPath)
End If
wr = My.Computer.FileSystem.OpenTextFileWriter(Me._configPath & _
Path.DirectorySeparatorChar & Me._configFileName, False)
wr.WriteLine(TextBoxBlogUrl.Text)
wr.WriteLine(CheckBoxJsInHeader.Checked.ToString)
Catch ex As Exception
risultato = False
Finally
If wr IsNot Nothing Then
wr.Close()
End If
End Try
End If
Return risultato
End Function
Per quanto riguarda la funzione:
Private Function UrlEncode(ByVal titolo As String, _
ByVal pattern As Regex, _
ByVal spaceReplacement As Char, _
ByVal escapePrefix As Char) As String
è stato abbastanza semplice estrapolarla dal binario di Community Server, grazie a Reflector for .NET ed è disponibile insieme al codice completo qui.
Nota bene: per chi non potesse o non volesse mettere nell'header HTML il tag script, è presente anche una checkbox apposita, il cui valore rimane memorizzato per la volta successiva. Attenzione: questa feature deve essere utilizzata in modalità HTML Code, posizionando il cursore al di fuori di qualsiasi container HTML, altrimenti il tag script verrà semplicemente ignorato da Live Writer. Non sono riuscito, infatti, a trovare un modo per costringere il plugin ad aggiugere il content alla fine di tutto il testo (forse non esiste).
Il plugin è scaricabile liberamente da qui (TechnoratiLinkCountWidget.zip - 12KB), mentre il codice completo è qui (TLCWplugin.zip - 570KB).
View blog reactions
[1] L'URL del blog deve comprendere anche la subdirectory dove vengono archiviati gli articoli, ad esempio su dotnetside per il mio blog si avrebbe: http://www.dotnetside.org/blogs/lucab/archive
Un esercizio potrebbe essere di aggiungere alle opzioni del plugin un piccolo parser che costruisca l'URL in base ad un formato impostato dall'utente.