HTML5 Video for BlogEngine

Добавил возможность загрузки видео в блоги на базе BlogEngine. Видео проигрывается при помощи новейшего HTML5 и скриптов техаского прогера MediaElementJS. Жаль что пока не работает для мобильных устройств, а то получилось бы очень удобная замена Youtube. Ниже видео танцующего медведя.


Метки: , ,   


BlogEngine 2.8

Вышла новая версия популярного блогового движка под .Net - BlogEngine. Как и с прошлой версией я не понимаю чем занимаются эти индусы во главе челом из Чикаго. По факту очередная версия с незначительными улучшениями (Обновление TinyMCE полезно, так как теперь позволяет вставлять видео с youtube корректно), в которой сохранились все теже Deadlock'и, что и ранее. Поэтому использовать движок в голом виде крайне не рекомендую. Не смотря на это он включен в каталог WebSites и предлагается многим для установки автоматически.

Последние три версии разработчики сосредоточились на одной фишке - Blog Aggregation, что по сути является сообществом, где общую ленту формируют несколько авторов, тем функционалом что и ранее был в движке.

Метки: ,   


Про Твиттер и BlogEngine.NET

Индус опубликовал переделанный виджет Твиттера для BlogEngine, Это обновление вызвано было тем, что 10 октября, как некоторые могут заметить, Твиттер отключил выдачу данных по RSS, в связи с постепенным переходом на API v1.1. Отключение RSS выдачи вызывает проблему в том, что согласно Twitterizer.NET ленту пользователя можно получать только через функцию UserTimeline, которая подразумевает передачу данных oauth, то есть с аутентификацией. Однако, как умно поступил индус, оказывается всю ленту пользователя Твиттера можно сгружать напрямую, только уже не в виде RSS, а в виде XML или JSON:
Старая ссылка на RSS - https://twitter.com/statuses/user_timeline/t30p.rss (Not Valid) Новая ссылка на XML - http://api.twitter.com/1/statuses/user_timeline/t30p.xml Новая ссылка на JSON - http://api.twitter.com/1/statuses/user_timeline/t30p.json
То есть данные благоразумно вынесены на отдельный домен и добавлена версионность в виде единички.

Завершить пост хотелось бы опусом разработчиков BlogEngine, которые на днях заявили о выходе следующей версии 2.7, в которой по сути ничего нового, кроме обновления внутренних скриптов написания постов. Да и предыдущая версия 2.6 - содержала единственно обновление в виде "Мультиблогов", фишка которая полностью содрана с wordpress'a и которая, на мой взгляд, внесла неразбериху и в код и саму работу блогодвижка. Когда общая лента одного блога может быть сформирована как совокупность блогов внутри базы. Очевидно, что это по логике дублирует мультипользовательность, когда к одному домену-блогу имеют доступ несколько пользователей и они дружно выкладывают материалы. То есть разработка BlogEngine полностью стагнировала, как и у wordpress, после того, как основные разработчики системы ушли работать в Микрософт, а на смену пришли индусы.

Чтобы не быть голословным озвучу основные проблемы для блогдвижков, которые также поставили себе разработчики ЖЖ и которые позволили бы двигаться дальше. Их всего 3.
  • Azure + Multi Roles - чтобы один блог обслуживало несколько инстансов, то есть чтобы была масштабируемость блога. По факту это было сделано для blogscloud.ru, а то что предлагается как NuGet Package - все таже ненадежная одноинстансовая установка и неможет называться полноценным переносом в облако.
  • Mobile - у BlogEngine есть нормальная мобильная версия, но никто так и не занялся нормальным масштабированием картинок на лету. Это сложная задача, и хотелось бы чтобы опытные индусы ее решили.
  • Комментарии - до сих пор нет нормального решения для большого количества комментариев к посту. Всякие централизованные системы типа DISQUS тоже не устраивают, и медленно грузятся, и хранят контент не у нас. Да и децентролизованные блогодвижки должны быть максимально независимыми.
Этот список можно продолжить, но даже эту малость никто не делает. И для поисковиков ссылка на инструкцию, как перейти с блога wordpress на блог BlogEngine.NET.


Метки: , , ,   


Syria and BlogEngine

Обнаружил, что один из пропагандистских ресурсов американцев против сирийского народа работает на движке BlogEngine. При этом в крупнейших западных изданиях идет ссылки, что независимые блогеры интернета против режима Асада. По этому случаю запустил блог про Войну в Сирии, куда копируется вся правда о происходящих там событиях. К слову, все работает на движке BlogEngine портированном в облако под именем BlogsCloud.ru.

Метки: ,   


BlogEngine 1.6

Обновил свой основной блог ya.topbot.ru до недавно вышедшей версии BlogEngine 1.6. За полторы недели продукт успели скачать 2500 раз, что и позволяет судить о количестве активных блогеров, ведущих блоги на этой платформе. Да, до популярности wordpress'a еще далеко, но будем продолжать клепать плагины для движка. В частности, мой плагин "MultiPost", по копированию постов во все места блогосферы, на базе BlogsApi работает без изменений кода.

В остальном же нововведений в новой версии почти нет, только внедрено управление комментариями, поддержка мульти-доменности и улучшена стабильность работы. И так как мне на оставляемые мне комментарии давно пофиг, то остается радоваться последнему. Про роль комментариев в блогосфере как-нить сделаю отдельный пост.

Метки: ,   


Tag Cloud for ASP.NET by jQuery

Ниже я раскажу как написать простенький компонент для отображения облака тэгов в ASP.NET. Пост содержит много кода. Для начала нам потребуется jQuery, который нужно где-нить подключиться на странице. Если Вы не знаете, что такое jQuery - то в поиск. Далее на странице, где у нас будет облако тэгов, а их может быть несколько, подключаем CSS и JS:
Copy Source | Copy HTML
  1. <%@ Register TagPrefix="lsv" TagName="TagCloud" Src="~\Components\_\TagCloud.ascx" %>
  2. <link rel="stylesheet" type="text/css" href="App_Themes/Basic/tagcloud/tagcloud.css" />
  3. <script type="text/javascript" src="App_Themes/Basic/tagcloud/tagcloud.js" charset="windows-1251"></script>

Где JS файл содержит следующий код:
Copy Source | Copy HTML
  1. function fontSize(freq, font_min, font_max,total_min,total_max) {
  2.     var size = (freq - total_min)*(font_max - font_min);
  3.     if(total_max-total_min>0){
  4.         size /= (total_max - total_min);
  5.     }
  6.     size += font_min;
  7.     if (size > font_max) size = font_max;
  8.     else {
  9.         if (size < font_min) size = font_min;
  10.     }
  11.     return size + "em";
  12. }
  13.  
  14. var iTagCloud = {
  15.     init: function(divName, data) {
  16.         $(document.createElement('ul')).attr("id", "list" + divName).attr("class", "tagList").appendTo("#" + divName);
  17.         //create tags
  18.         $.each(data.tags, function(i, val) {
  19.             //create item
  20.         var li = $(document.createElement('li'));
  21.             //create link
  22.             $(document.createElement('a')).css("font-size",
  23.                  fontSize(val.freq, 0.5, 2, data.min,data.max)
  24.             ).text(val.tag).attr({ title: val.freq + " упоминаний",
  25.                 target: "_blank",
  26.                 href: "http://blogs.yandex.ru/search.xml?text=" + val.tag
  27.             }).appendTo(li);
  28.             //add to list
  29.             li.appendTo("#list" + divName);
  30.         });
  31.     }
  32. };

Файл стилей на ваше успотрение раставляет цвета, например:
Copy Source | Copy HTML
  1. div.TagCloud { width:90%; background-color:#ffffff;
  2.                            text-align:center; padding:0px; overflow:auto; font-size:100%; font-family:arial; }
  3. ul.tagList { margin:0; padding:0; }
  4. ul.tagList li { list-style-type:none; float:left; margin:0 3px; height:35px; }
  5. ul.tagList li a { text-decoration:none; color:#0000FF; }
  6. ul.tagList li a:hover { text-decoration:underline; color:Red }


Теперь можно перейти к содержимому самого компонента TagCloud.aspx, основная задача которого просто заполнить текст в метку Content:
Copy Source | Copy HTML
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Web;
  5. using System.Web.UI;
  6. using System.Web.UI.WebControls;
  7.  
  8. public partial class Components__TagCloud : System.Web.UI.UserControl
  9. {
  10.     #region Переменные
  11.  
  12.     DataSet _wordsdata = null;
  13.     String _title = String.Empty;
  14.  
  15.     #endregion
  16.  
  17.     #region Свойства
  18.  
  19.     public DataSet WordsData
  20.     {
  21.         set
  22.         {
  23.             _wordsdata = value;
  24.         }
  25.         get
  26.         {
  27.             return _wordsdata;
  28.         }
  29.     }
  30.  
  31.     public String Title
  32.     {
  33.         set
  34.         {
  35.             _title = value;
  36.         }
  37.         get
  38.         {
  39.             return _title;
  40.         }
  41.     }
  42.  
  43.     #endregion
  44.  
  45.     /// <summary>
  46.     /// Пошла загрузка
  47.     /// </summary>
  48.     /// <param name="sender"></param>
  49.     /// <param name="e"></param>
  50.     protected void Page_Load(object sender, EventArgs e)
  51.     {
  52.         Content.Text = "<div id=\"tagCloud"+this.ID+"\" class=\"TagCloud\">";
  53.         if(!String.IsNullOrEmpty(this.Title)){
  54.             Content.Text += "<h3>"+this.Title+"</h3>";
  55.         }
  56.         Content.Text += "</div><br/>";
  57.         if (WordsData != null && WordsData.Tables.Count > 0
  58.             && WordsData.Tables[0].Rows.Count > 0)
  59.         {
  60.             Content.Text += "<script type=\"text/javascript\">";
  61.             Content.Text += "$(function() {   " +
  62.            "iTagCloud.init('tagCloud"+this.ID+"', {tags:[";
  63.             int Min = int.MaxValue;
  64.             int Max = int.MinValue;
  65.  
  66.             //пройдемся по всем данным.
  67.             foreach(DataRow dr in WordsData.Tables[0].Rows){
  68.                 // $json .= "{tag:'" . $row["tag"] . "',freq:'" . $row["frequency"] . "'}";   
  69.                 Content.Text += "{tag:'" + dr[0].ToString()+"',freq:'"+ dr[1].ToString()+"'},";
  70.                 int freq;
  71.                 if(int.TryParse(dr[1].ToString(),out freq)){
  72.                     if (freq < Min)
  73.                     {
  74.                         Min = freq;
  75.                     }
  76.                     else if(freq > Max)
  77.                     {
  78.                         Max = freq;
  79.                     }
  80.                 }
  81.             }
  82.             Content.Text = Content.Text.TrimEnd(',')
  83.                 + "],min:'"+Min.ToString()+"',max:'"+Max.ToString()+"'})});"; //закрытие объекта
  84.             Content.Text += "</script>";
  85.         }else{
  86.             Content.Text += "-- недостаточно данных для построения облака слов --";
  87.         }
  88.  
  89.     }
  90. }
  91.  
  92.  
  93.  

Вот и все, теперь задав DataSet у объекта TagCloud мы получим полноченное облако тэгов. При желании можете написать виджет для замены унылого облака тэгов которое есть в BlogEngine =).

Метки: , ,   


BlogEngine - MultiPost v0.1.4

Обновил расширение для BlogEngine позволяющее постить одновременно сразу в несколько блогомест. Качать архив файлов тут. Архив содержит 3 вещи, которые нужно поместить в корень вашего блога заменив старое - 1) папку admin с измененным файлом Settings, я там исправил ошибку с редактированием настроек 2) папку App_Code - в ней содержится код расширения, который вы сможете поправить, если нужно 3) Папку bin, которая содержит библиотеку BlogsAPI, в которой реализована работа с основными(lj,liru,yaru,blogsmail) блогохостингами.

Не забудте внести изменения в web.config, как я писал ранее, а то не будет работать с Яндексом.

Сейчас там реализовано копирование постов на Лиру и Яру. Но если есть желание, то можно дописать свой класс для копирования постов куда угодно, который имеет интерфейс IBlogHosting и поместить его в namespace MultiPostExtension.Blogs:
Copy Source | Copy HTML
  1. public interface IBlogHosting
  2.     {
  3.         /// <summary>
  4.         /// NameOf
  5.         /// </summary>
  6.         String Name
  7.         {
  8.             get;
  9.         }
  10.  
  11.         /// <summary>
  12.         /// Settings
  13.         /// </summary>
  14.         SettingsCollection Defaults();
  15.  
  16.  
  17.         /// <summary>
  18.         /// Logging action
  19.         /// </summary>
  20.         /// <returns>isSuccess</returns>
  21.         Boolean PublishItem(IPublishable post);
  22.  
  23.     }

В моем случае реализация для Лиру выглядит достаточно просто:
Copy Source | Copy HTML
  1. namespace MultiPostExtension.Blogs
  2. {
  3.  
  4.     #region using
  5.  
  6.     using System;
  7.     using System.IO;
  8.     using System.Web;
  9.     using System.Net;
  10.     using System.Text;
  11.     using BlogEngine.Core;
  12.     using BlogEngine.Core.Web.Controls;
  13.     using System.Threading;
  14.     using BlogsAPI;
  15.  
  16.     #endregion
  17.  
  18. /// <summary>
  19. /// Design a post to somewhere
  20. /// </summary>
  21.     public class LiveInternet : BlogHosting, IBlogHosting
  22.     {
  23.         /// <summary>
  24.         /// NameOf
  25.         /// </summary>
  26.         public String Name
  27.         {
  28.             get
  29.             {
  30.                 return "LiveInternet";
  31.             }
  32.         }
  33.  
  34.         /// <summary>
  35.         /// Defs
  36.         /// </summary>
  37.         /// <returns></returns>
  38.         public SettingsCollection Defaults()
  39.         {
  40.             SettingsCollection sc = new SettingsCollection();
  41.             sc.Add("true");
  42.             sc.Add(" ");
  43.             sc.Add(this.Name);
  44.             sc.Add("flashr");
  45.             sc.Add("");
  46.             sc.Add("noneed");
  47.             sc.Add("1");
  48.             sc.Add(PostType.Public.ToString());
  49.             return sc;
  50.         }
  51.  
  52.         /// <summary>
  53.         /// Constructor with params
  54.         /// </summary>
  55.         /// <param name="sets"></param>
  56.         public LiveInternet(SettingsCollection settings)
  57.         {
  58.             this.Hostname = "liveinternet.ru";
  59.             this.blogSettings = settings;
  60.         }
  61.  
  62.         /// <summary>
  63.         /// Making it published
  64.         /// </summary>
  65.         /// <param name="post">Object of Post</param>
  66.         public Boolean PublishItem(IPublishable post)
  67.         {
  68.             Boolean bRet = false;
  69.             if (this.AttemptsCount++ < this.blogSettings.Attempts)
  70.             {
  71.                 if (this.MakePost(post))
  72.                 {
  73.                     bRet = true;
  74.                 }
  75.             }
  76.             else
  77.             {
  78.                 throw new NotSupportedException("Too many fails for " + post.Title);
  79.             }
  80.             return bRet;
  81.         }
  82.  
  83.         /// <summary>
  84.         /// Hidden actions 
  85.         /// </summary>
  86.         /// <param name="post">Published content</param>
  87.         /// <returns></returns>
  88.         private Boolean MakePost(IPublishable post)
  89.         {
  90.             BlogPost blogPost = new BlogPost(){
  91.                  Subject = post.Title,
  92.                  Body = post.Content,
  93.                  type = post.IsPublished?PostType.Public:PostType.Private
  94.             };
  95.             //adding tags
  96.             blogPost.Attributes.Add(new PostAttribute(PostParams.Tags.ToString(),GetTags(post.Categories)));
  97.  
  98.             BlogsAPI.Sites.Liveinternet liru = new BlogsAPI.Sites.Liveinternet();
  99.             return liru.MakePost(new UserAccaunt(
  100.                 this.blogSettings.Login,
  101.                 this.blogSettings.Password
  102.                 ), blogPost
  103.                 ).Success;
  104.         }
  105.     }
  106. }

Метки: , ,   


BlogEngine - MultiPost

Как вы знаете, я транслирую свои записи в несколько популярных блогомест в интернете (за исключением ЖЖ). И это я делаю не с помощью встроенных средств копирования постов, а с помощью самописного модуля (Extension) для движка BlogEngine.Net, работающем под ASP.NET. Почему так? Да потому, что часто при задании копирования постов с помощью сторонних средств, таких как трансляции на Ya.ru, посты помещаемые в блог выпадают из поискового индекса и ссылки в них не учитываются.

1) Модуль получился расширяемый, что позволяет дописать копирование постов куда вам только захочется. В вышеуказанный архив я вложил только два класса, которые позволяют копировать посты на Хабр и на Ярушечку. Так как ярушечка имеет херовый интерфейс, и код выглядит очень сложным (и, кстати, не рабовает, если Вы недавно заходили под своим же аккаунтом с другой машины), а также придется добавить следующие строчки в web.config:
  1. <system.net>
  2.    <settings>
  3.      <httpWebRequest useUnsafeHeaderParsing="true" />
  4.      <servicePointManager expect100Continue="false"/>
  5.    </settings>
  6.  </system.net>


2) Далее, в настройках интерфейса у Вас появится такая вот картинка, позволяющая настроить копирование постов:
Отмечу, что все селективные значения прописываются вручную, так как в текущей версии BlogEngine нет возможностей задавать параметр группы настроек модуля в виде выбора из списка. Надеюсь это сделают в следующей версии, которую обещают в конце Января. Итак, первый параметр отвечает за копирование постов (bool=true/false). Второй за название категории, только из которой , если она указана, будут копироваться посты. Третий - BlogType - название класса отвечающего за копирование. Далее логин-пароль в удаленной системе. BlogParam - это дополнительный параметр, который может понадобиться для определенный систем. В частности для Яндекса я передаю номер моего блога, чтобы не извлекать его каждый раз. MaxAttempts(int) - количество попыток кпирования поста, если неуспех, то бросаем это дело. Последний - PostType(Public/Private/OnlyFriends) - тип записи. Как видим, для хабра стоит Private, что означает, что я планирую подредактировать запись и вставить в нее кат, прежде чем делать общедоступной хабрачанцам.

3) Из внутренностей работы модуля рассмотрю класс MultiPost:
  1. namespace MultiPost
  2. {
  3.     /// <summary>
  4.     /// Copy all your posts to blog at http://wow.ya.ru
  5.     /// You have to assign an login and password inside constructor
  6.     /// </summary>
  7.     /// <remarks>
  8.     /// Based on pure code - http://ya.topbot.ru
  9.     /// </remarks>
  10.     [Extension("Copy all your posts to blogs", "0.1.1", "http://ya.topbot.ru")]
  11.     public class MultiPost
  12.     {
  13.         /// <summary>
  14.         /// Name of Module
  15.         /// </summary>
  16.         private static String sExtensionName = "MultiPost";
  17.  
  18.         static protected ExtensionSettings _settings = null;
  19.         /*....*/
  20.     }
  21. }
Как видим к атрибутах класса задаются настройки модуля, чтобы BlogEngine его воспринял как расширение. Конструктор достаточно простой, подписываемся на событие о размещении нового поста:
  1. static MultiPost()
  2. {
  3.     // create settings object. You need to pass exactly your
  4.     // extension class name (case sencitive)
  5.     ExtensionSettings settings = new ExtensionSettings(sExtensionName);
  6.     /*.......*/
  7.     ExtensionManager.ImportSettings(settings);
  8.     _settings = ExtensionManager.GetSettings(sExtensionName);
  9.     Post.Saved += new EventHandler<SavedEventArgs>(Post_Saved);
  10. }
И кода происходит новый пост, то обрабатываем его создавая новый поток для этих целей
  1. /// <summary>
  2.  /// Occurs on new post
  3.  /// <remarks>
  4.  /// It opens a new thread and executes
  5.  /// because it takes some time to complete.
  6.  /// </remarks>
  7.  /// </summary>
  8.  private static void Post_Saved(object sender, SavedEventArgs e)
  9.  {
  10.      if (e.Action != SaveAction.Insert)
  11.          return;
  12.  
  13.      IPublishable item = (IPublishable)sender;
  14.  
  15.      //(HttpContext.Current == null || !HttpContext.Current.Request.IsLocal)
  16.      if (item.IsVisible)
  17.      {
  18.          //getting new thread to do all crap
  19.           ThreadPool.QueueUserWorkItem(delegate {
  20.               PostCycle(item);
  21.           });
  22.      }
  23.  }
  24.  
  25.  /// <summary>
  26.  /// Main process to post
  27.  /// </summary>
  28.  /// <param name="item"></param>
  29.  public static void PostCycle(IPublishable item){
  30.      //перечисляем все блоги, что активны...
  31.      DataTable dt = _settings.GetDataTable();
  32.      //pure code, coz i cant change Categories class
  33.      string catsline = "";
  34.      foreach (Category c in item.Categories)
  35.      {
  36.          catsline += c.Title + ",";
  37.      }
  38.      for (int i = dt.Rows.Count - 1; i >= 0; i--)
  39.      {
  40.          SettingsCollection sets = new SettingsCollection(dt.Rows[i].ItemArray);
  41.          if (!sets.Enabled ||
  42.              (!String.IsNullOrEmpty(sets.CopyOnTags.Trim())
  43.              && catsline.Contains(sets.CopyOnTags))
  44.              )
  45.          {//not allowed to post
  46.              dt.Rows.RemoveAt(i);
  47.          }
  48.      }
  49.      //lets finish the others
  50.      for (int i = dt.Rows.Count - 1; dt.Rows.Count > 0; i--)
  51.      {
  52.          Thread.Sleep(5000);
  53.          if (i < 0)//new cycle...bad...bad
  54.          {
  55.              i = dt.Rows.Count;
  56.          }
  57.          try
  58.          {
  59.              SettingsCollection sets = new SettingsCollection(dt.Rows[i].ItemArray);
  60.              Type tBlog = Type.GetType("MultiPost.Blogs."+sets.BlogType);
  61.              Object theBlog = Activator.CreateInstance(tBlog,
  62.                  new object[1] { sets });
  63.              Type[] paramTypes = {
  64.                      typeof(IPublishable)
  65.                        };
  66.              Object[] ps = new object[1] { item };
  67.              MethodInfo mDefaults = tBlog.GetMethod("ProcessItem", paramTypes);
  68.              if ((Boolean)mDefaults.Invoke(theBlog, ps))//if success
  69.              {
  70.                  dt.Rows.RemoveAt(i);
  71.              }
  72.              //failed, lets continue
  73.          }
  74.          catch(Exception e)//toomanyattempts
  75.          {
  76.              dt.Rows.RemoveAt(i);
  77.          }
  78.  
  79.      }
  80.  }
В основном цикле, мы создаем объект класса, и вызываем нужную функцию размещения поста, передавая ей настройки. Как видно попытка разместить пост происходит каждые 5 секунд. Обработка объектов идет на объекте Rows, что некрасиво, но просто. Остальное Вы можете найти внутри выложенного архива. PS: Чтобы иметь возможность править настройки рекомендую заменить компонент admin/Extension Manager/Settings.ascx.cs на файл, вложенный архив. Там исправлена некоторая ошибка в функции редактирования настроек.


Метки: , ,   


Кто я?

Программист. Я слежу за блогосферой и знаю, как будет развиваться интернет. Когда у меня есть время я даже прилагаю для этого усилия. Подробнее

Последние комментарии

Не отображать

Topbot at FeedsBurner
 

копирайт

Все мысли, высказанные в блоге, являются моим мнением и за это мнение меня никто не забанит! Кроме того, никто не имеет право копировать материалы блога без использования ctrl+C/V!

© Copyright 2008