2022年3月5日土曜日

アーカイブガジェットの魔改造

Blogger のアーカイブガジェットを魔改造してみました。

※この魔改造アーカイブガジェットは、レイアウトバージョン3、ガジェットバージョン2の Blogger テーマ用です。


アーカイブのサンプル画像
魔改造したアーカイブガジェット。
左はスタイルを階層にしたときで、
右はスタイルをフラットリストまたはプルダウンメニューにしたとき。


魔改造した理由

Blogger 標準のアーカイブガジェットは、全項目を上から順に一覧にしようとするので、例えば「一つ前の月の投稿を見たい」と思ったとき、縦に長く伸びたサイドバーを下へ下へとスクロールしながら目的のアーカイブ期間を探さなければいけません。

そのままでは次のアーカイブ、前のアーカイブへと移動したいときにサクッと選べなくて不便だなと思いました。

そこで、前後のアーカイブ期間にはスムーズに切り替えられるようにしよう! それと投稿数が増えてもアーカイブガジェットが縦に伸びすぎるのも防ごう! というのが今回の主な目的です。

最終的には結構シンプルな形になったと思いますが、何度も作り直してボツにした試行錯誤の期間も含めると、実はかなり長い時間をかけて作りました。

導入方法

  1. テーマのバックアップ

    作業をする前に、万が一のためにテーマのバックアップを取っておきましょう。やり方は、[テーマ]→[バックアップ](カスタマイズの右の▼をクリックして選ぶ)→[ダウンロード]です。

  2. アーカイブガジェットの追加と設定

    [レイアウト] で目的の場所にアーカイブガジェット追加し、スタイル等を好みに合わせて設定してください。おすすめは、[スタイル] を [階層]、 [投稿のタイトルを表示する] を [ON] 、[最も古い投稿を最初に表示する] を [ON] にしたものです。

  3. テーマを書き換える

    [テーマ]→[HTMLを編集](カスタマイズの右の▼をクリックして選ぶ)→[ウィジェットに移動]→アーカイブガジェット名を選択し、<b:widget>~</b:widget> の中にある <b:includable> 要素をすべて削除し、その代わりに下のソースコードをコピペしてください。

ソースコード
<b:includable id='main' var='this'>
  <b:class expr:name='data:style'/>
  <b:with var='mode' value='(data:view.isMultipleItems or data:view.isPost) ? ((data:view.isArchive and not data:view.archive.month) ? 1 : ((data:view.isArchive and not data:view.archive.day) ? 2 : 3)) : 0'>
    <details class='collapsible extendable'>
      <b:attr cond='data:mode neq 0' name='open' value='open'/>
      <b:with value='true' var='renderAsDetails'>
      <b:with value='data:messages.archive' var='defaultTitle'>
        <b:include name='super.main'/>
      </b:with>
      </b:with>
    </details>
  </b:with>
</b:includable>
<b:includable id='content'>
  <div class='widget-content'>
    <b:include name='hierarchy' cond='data:style eq "HIERARCHY"'/>
    <b:include name='flat' cond='data:style eq "FLAT"'/>
    <b:include name='menu' cond='data:style eq "MENU"'/>
    <b:include name='scripts'/>
  </div>
</b:includable>
<b:includable id='hierarchy'>
  <div expr:id='data:widget.instanceId + &quot;_Navigation&quot;' class='archive-navigation'>
    <b:if cond='(data:mode neq 0) and (data:data any p => p.expclass eq "expanded")'>
      <b:include name='navSearch' data='{curr: data:this}'/>
    <b:else/>
      <b:with var='prev' value='data:data ? (data:data.first.data.first.data.first ?: data:data.first.data.first ?: data:data.first) : data:undefined'>
      <b:with var='next' value='data:data ? (data:data.last.data.last.data.last ?: data:data.last.data.last ?: data:data.last) : data:undefined'>
        <b:include name='navLinks' data='{curr: data:this, prev: data:prev, next: data:next, name: data:messages.blogArchive, prevname: data:messages.oldest, nextname: data:messages.newest}'/>
      </b:with>
      </b:with>
    </b:if>
  </div>
  <hr class='nav-separator'/>
  <div expr:id='data:widget.instanceId + &quot;_ArchiveList&quot;' class='archive-index hierarchy'>
    <b:include name='viewTree' data='{curr: data:this, step: 0, last: data:mode}'/>
  </div>
  <div expr:id='data:widget.instanceId + &quot;_Posts&quot;' class='archive-posts'>
    <b:include name='viewPosts' data='data:this'/>
  </div>
</b:includable>
<b:includable id='flat'>
  <div expr:id='data:widget.instanceId + &quot;_ArchiveList&quot;' class='archive-index flat'>
    <b:include name='flatmenu'/>
  </div>
</b:includable>
<b:includable id='menu'>
  <div expr:id='data:widget.instanceId + &quot;_ArchiveList&quot;' class='archive-index menu'>
    <b:include name='flatmenu'/>
  </div>
</b:includable>
<b:includable id='navSearch' var='args'>
  <b:with var='list' value='data:args.curr.data'>
    <b:loop index='i' values='data:list' var='item'>
      <b:if cond='data:item.expclass eq "expanded"'>
        <b:with var='prev' value='(data:i gt 0) ? data:list[data:i - 1] : (data:args.prev.data ? data:args.prev.data.last : data:args.prev)'>
        <b:with var='next' value='(data:i + 1 lt data:list.size) ? data:list[data:i + 1] : (data:args.next.data ? data:args.next.data.first : data:args.next)'>
        <b:with var='currname' value='data:args.name + " " + data:item.name'>
        <b:with var='prevname' value='(data:i gt 0 ? "" : data:args.prevname) + (data:prev ? " " + data:prev.name : "")'>
        <b:with var='nextname' value='((data:i + 1) lt data:list.size ? "" : data:args.nextname) + (data:next ? " " + data:next.name : "")'>
          <b:if cond='(data:item.url.canonical eq data:view.url.canonical) or not data:item.data or (data:item.data none p => p.expclass eq "expanded")'>
            <b:include name='navLinks' data='{curr: data:item, prev: data:prev, next: data:next, name: data:currname, prevname: data:prevname, nextname: data:nextname}'/>
            <b:else/>
            <b:include name='navSearch' data='{curr: data:item, prev: data:prev, next: data:next, name: data:currname, prevname: data:prevname, nextname: data:nextname}'/>
          </b:if>
        </b:with>
        </b:with>
        </b:with>
        </b:with>
        </b:with>
      </b:if>
    </b:loop>
  </b:with>
</b:includable>
<b:includable id='navLinks' var='args'>
  <div style='font-size: 16px; line-height: normal;'>
    <div style='text-align: center;'>
      <b:include name='linker' data='{url: data:args.curr.url, name: data:args.name, class: "archive-name", tagName: "b"}'/>
    </div>
    <div style='float: left;'>
      <b:include name='linker' data='{url: data:args.prev.url, name: data:args.prevname, class: "prev-link"}' cond='data:args.prev'/>
      <b:if cond='not data:args.prev'><span class='prev-link oldest'><data:messages.oldest/></span></b:if>
    </div>
    <div style='float: right;'>
      <b:include name='linker' data='{url: data:args.next.url, name: data:args.nextname, class: "next-link"}' cond='data:args.next'/>
      <b:if cond='not data:args.next'><span class='next-link newest'><data:messages.newest/></span></b:if>
    </div>
  </div>
  <div style='clear: both;'/>
</b:includable>
<b:includable id='viewTree' var='args'>
  <b:if cond='data:args.curr.data'>
    <b:include name='listmenu' data='{name: ("index-menu" + data:args.step), list: data:args.curr.data, reset: (data:args.step >= data:args.last), firstLabel: (data:args.step == 0 ? "------" : "ALL"), firstUrl: (data:args.step == 0 ? "" : data:args.curr.url)}'/>
    <b:with var='next' value='data:args.curr.data first p => p.expclass eq "expanded"'>
      <b:if cond='data:next and (data:args.step lt data:args.last)'>
        <b:include name='viewTree' data='{curr: data:next, step: (data:args.step + 1), last: data:args.last}'/>
      </b:if>
    </b:with>
    <b:else/>
  </b:if>
</b:includable>
<b:includable id='viewPosts' var='node'>
  <div class='page_list'>
    <ol>
      <b:include name='viewPostsLoop' data='data:node'/>
    </ol>
    <button class='prev' type='button' onclick='mdgListPagePrev(this.parentElement);'>
      &lt;
    </button>
    <button class='next' type='button' onclick='mdgListPageNext(this.parentElement);'>
      &gt;
    </button>
    <div style='clear: both;'/>
  </div>
</b:includable>
<b:includable id='viewPostsLoop' var='node'>
  <b:if cond='data:node.posts'>
    <b:loop var='post' values='data:node.posts'>
      <li expr:class='data:post.url.canonical eq data:view.url.canonical ? "expanded" : "collapsed"'><b:include name='linker' data='data:post'/></li>
    </b:loop>
  </b:if>
  <b:if cond='data:node.data'>
    <b:loop var='item' values='data:node.data'>
      <b:include name='viewPostsLoop' data='data:item'/>
    </b:loop>
  </b:if>
</b:includable>
<b:includable id='flatmenu'>
  <b:with var='tabmode' value='((data:data.first.url.canonical fragment "f") contains "archive.html") ? "YMD" : "YM"'>
    <div class='tab_view'>
      <div class='tabs'>
        <input expr:id='data:widget.instanceId + "_Tab-yearly"' class='tab_button' type='radio' expr:name='data:widget.instanceId + "_Tab"' value='yearly' onchange='mdgTabChange(this.parentElement.parentElement.parentElement, this.name, this.value);'>
          <b:attr name='checked' value='checked' cond='data:mode eq 1'/>
        </input>
        <label expr:for='data:widget.instanceId + "_Tab-yearly"'>
          <span>Yearly</span>
        </label>
        <input expr:id='data:widget.instanceId + "_Tab-monthly"' class='tab_button' type='radio' expr:name='data:widget.instanceId + "_Tab"' value='monthly' onchange='mdgTabChange(this.parentElement.parentElement.parentElement, this.name, this.value);'>
          <b:attr name='checked' value='checked' cond='data:mode eq 2 or (data:mode neq 1 and data:tabmode eq "YM")'/>
        </input>
        <label expr:for='data:widget.instanceId + "_Tab-monthly"'>
          <span>Monthly</span>
        </label>
        <b:if cond='data:tabmode eq "YMD"'>
          <input expr:id='data:widget.instanceId + "_Tab-detail"' class='tab_button' type='radio' expr:name='data:widget.instanceId + "_Tab"' value='detail' onchange='mdgTabChange(this.parentElement.parentElement.parentElement, this.name, this.value);'>
            <b:attr name='checked' value='checked' cond='data:mode in [0, 3]'/>
          </input>
          <label expr:for='data:widget.instanceId + "_Tab-detail"'>
            <span>Detail</span>
          </label>
        </b:if>
      </div>
      <b:with var='padYear' value='"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"'>
      <b:with var='padMonth' value='"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"'>
      <b:with var='urlLength' value='data:blog.homepageUrl.canonical.size'>
      <b:with var='paddedList' value='data:data select p => { paddedUrl: [((data:padYear + (data:p.url.canonical fragment "f")) snippet {length: (data:urlLength + 50)}), ((data:padMonth + (data:p.url.canonical fragment "f")) snippet {length: (data:urlLength + 50)}), (data:p.url.canonical fragment "f")], item: data:p }'>
      <b:with var='expandedData' value='data:paddedList first p => p.item.expclass eq "expanded"'>
        <b:include name='flatmenuList' data='{ paddedList: data:paddedList, tabname: "yearly", hidden: (data:mode neq 1), expandedUrl: (data:expandedData.paddedUrl[0]), layer: 0 }'/>
        <b:include name='flatmenuList' data='{ paddedList: data:paddedList, tabname: "monthly", hidden: (not (data:mode eq 2 or (data:mode in [0, 3] and data:tabmode eq "YM"))), expandedUrl: (data:expandedData.paddedUrl[data:tabmode eq "YM" ? 2 : 1]), layer: (data:tabmode eq "YM" ? 2 : 1) }'/>
        <b:include name='flatmenuList' cond='data:tabmode eq "YMD"' data='{ paddedList: data:paddedList, tabname: "detail", hidden: (not (data:mode in [0, 3])), expandedUrl: (data:expandedData.paddedUrl[2]), layer: 2 }'/>
      </b:with>
      </b:with>
      </b:with>
      </b:with>
      </b:with>
    </div>
  </b:with>
</b:includable>
<b:includable id='flatmenuList' var='args'>
  <div expr:class='"page_list " + data:args.tabname' expr:data-tab='data:widget.instanceId + "_Tab-" + data:args.tabname'>
    <b:attr name='hidden' value='hidden' cond='data:args.hidden'/>
    <ul>
      <b:loop values='data:data' var='item' index='i'>
        <b:with var='prevUrl' value='data:i eq 0 ? "first" : (data:args.paddedList[data:i - 1].paddedUrl[data:args.layer])'>
        <b:with var='thisUrl' value='data:args.paddedList[data:i].paddedUrl[data:args.layer]'>
          <b:if cond='data:prevUrl neq data:thisUrl'>
            <li>
              <b:class expr:name='(data:args.expandedUrl eq data:thisUrl) ? "expanded" : "collapsed"'/>
              <b:include name='linker' data='{ name: data:item.name, url: data:item.url, tagName: "a" }'/>
            </li>
          </b:if>
        </b:with>
        </b:with>
      </b:loop>
    </ul>
    <button class='prev' type='button' onclick='mdgListPagePrev(this.parentElement);'>
      &lt;
    </button>
    <button class='next' type='button' onclick='mdgListPageNext(this.parentElement);'>
      &gt;
    </button>
    <div style='clear: both;'/>
  </div>
</b:includable>
<b:includable id='listmenu' var='args'>
  <select expr:class='data:args.class' expr:name='data:args.name' onchange='if (this.value != "") window.location = this.value;'>
    <b:if cond='data:args.firstLabel'>
      <option expr:value='data:args.firstUrl' class='default'><b:attr name='selected' value='selected' cond='data:args.reset'/><data:args.firstLabel/></option>
    </b:if>
    <b:loop values='data:args.list' var='item'>
      <option expr:value='data:item.url' expr:class='data:item.expclass + "item"'><b:attr name='selected' value='selected' cond='data:item.expclass eq "expanded" and not data:args.reset'/><data:item.name/></option>
    </b:loop>
  </select>
</b:includable>
<b:includable id='linker' var='args'>
  <b:with var='tagName' value='data:args.tagName ?: ((data:args.url eq data:view.url) ? "b" : "a")'>
    <b:tag expr:class='"link" + (data:args.class ? " " + data:args.class : "") + (data:args.post-count ? " post-count-link" : "")' expr:name='data:tagName'>
      <b:attr expr:name='(data:tagName eq "a") ? "href" : "data-href"' expr:value='data:args.url.escaped'/>
      <b:eval expr='data:args.name ? data:args.name.escaped : (data:args.title ? data:args.title.escaped : data:args.url.escaped)'/>
    </b:tag>
    <b:if cond='data:args.post-count'>
      <span class='post-count'><data:args.post-count/></span>
    </b:if>
  </b:with>
</b:includable>
<b:includable id='scripts'>
  <script>
    (function(w){
      const pageMode = '<data:mode/>', widgetElem = document.getElementById('<data:widget.instanceId/>');
      const archiveStyle = '<data:style/>', tabMode = '<b:eval expr='((data:data.first.url.canonical fragment "f") contains "archive.html") ? "YMD" : "YD"'/>';

      w.mdgHidden = function(elem, isHidden) {
        if (isHidden) elem.setAttribute('hidden', 'hidden');
        else elem.removeAttribute('hidden');
      };
      
      w.mdgTabChange = function(elem, name, value) {
        var contents = elem.querySelectorAll('[data-tab|=' + name + ']');
        for (const content of contents) {
          w.mdgHidden(content, content.dataset.tab != name + '-' + value);
        }
      };
      w.mdgTabViewInit = function(elem) {
        var inputs = elem.querySelectorAll('.tab_button');
        var checked = elem.querySelector('.tab_button:checked');
        if (checked) w.mdgTabChange(elem, checked.name, checked.value);
        else {
          var content = elem.querySelector('[data-tab]');
          var nameValue = content.dataset.tab.split('-');
          if (nameValue.length == 2) w.mdgTabChange(elem, nameValue[0], nameValue[1]);
        }
      };
      w.mdgListPageSet = function(elem, pos, size) {
        var items = elem.querySelectorAll('li');
        var len = items.length;
        var p = pos &lt; 0 ? 0 : ((pos &gt; len - size) ? len - size : pos);
        for(var i=0; i &lt; len; i++) {
          w.mdgHidden(items[i], i &gt;= p + size || i &lt; p);
        }
        elem.dataset.position = p;
        var prevButton = elem.querySelector('.prev');
        var nextButton = elem.querySelector('.next');
        if (prevButton) prevButton.disabled = p &lt;= 0;
        if (nextButton) nextButton.disabled = p + size &gt;= len;
      };
      w.mdgListPageTurn = function(elem, nextprev) {
        var size = elem.dataset.size ? parseInt(elem.dataset.size) : 5;
        if (isNaN(size)) size = 5;
        var pos = elem.dataset.position ? parseInt(elem.dataset.position) : 0;
        if (isNaN(pos)) pos = 0;
        w.mdgListPageSet(elem, pos + (nextprev == "next" ? size : (nextprev == "prev" ? -size : parseInt(nextprev, 10))), size);
      }
      w.mdgListPageNext = function(elem) {
        w.mdgListPageTurn(elem, 'next');
      };
      w.mdgListPagePrev = function(elem) {
        w.mdgListPageTurn(elem, 'prev');
      };
      w.mdgListPagingInit = function(elem) {
        var expItem = elem.querySelector('.expanded');
        if (!expItem) expItem = elem.querySelector('li'); 
        var items = [].slice.call(expItem.parentElement.querySelectorAll('li'));
        var num = items.indexOf(expItem);
        var size = elem.dataset.size ? parseInt(elem.dataset.size) : 5;
        var pos = num - (size - (size % 2)) / 2;
        w.mdgListPageSet(elem, pos, size);                  
        for (var i=0; i &lt; items.length; i++) {
          items[i].value = i+1;
        }
      };
      w.mdgRenameLink = function(parentElem, selector, param, nameFormat, urlFormat) {
        const regexp = /\/([0-9]{4})_([0-9]{2})_([0-9]{2})_archive.html|\/([0-9]{4})\/(?:([0-9]{2})\/)/;
        var elems = parentElem.querySelectorAll(selector);
        for (const elem of elems) {
          var url = param in elem ? elem[param] : elem.dataset[param];
          if (!url || !regexp.test(url)) return;
          var ret = regexp.exec(url);
          var yMd = { yyyy: ret[1] || ret[4], MM: ret[2] || ret[5], dd: ret[3], NNNN: elem.textContent };
          if (urlFormat) {
            var newUrl = url.replace(regexp, urlFormat.replace(/yyyy|MM|dd|NNNN/g, (t) =&gt; { return yMd[t]; }));
            if (param in elem) elem[param] = newUrl; else elem.dataset[param] = newUrl;
          }
          if (nameFormat) {
            elem.textContent = nameFormat.replace(/yyyy|MM|dd|NNNN/g, (t) =&gt; { return yMd[t]; });
          }
        }
      };

      if (widgetElem) {
        if (archiveStyle == 'HIERARCHY') {
          var pageLists = widgetElem.querySelectorAll('#<data:widget.instanceId/>_Posts .page_list');
          for (const pageList of pageLists) {
            w.mdgListPagingInit(pageList);
          }
        }
        else {
          var pageLists = widgetElem.querySelectorAll('#<data:widget.instanceId/>_ArchiveList .page_list');
          for (const pageList of pageLists) {
            w.mdgListPagingInit(pageList);
          }
          var tabViews = widgetElem.querySelectorAll('#<data:widget.instanceId/>_ArchiveList .tab_view');
          for (const tabView of tabViews) {
            w.mdgTabViewInit(tabView);
          }
          const nameFormats = [null, 'yyyy', 'yyyy MM', 'yyyy NNNN'];
          const urlFormats = [null, '/yyyy/', '/yyyy/MM/', null];
          w.mdgRenameLink(widgetElem, '.archive-index .yearly .link', 'href', nameFormats[1], urlFormats[1]);
          if (tabMode == 'YMD') {
            w.mdgRenameLink(widgetElem, '.archive-index .monthly .link', 'href', nameFormats[2], urlFormats[2]);
            w.mdgRenameLink(widgetElem, '.archive-index .detail .link', 'href', nameFormats[3], urlFormats[3]);
          }
          else {
            w.mdgRenameLink(widgetElem, '.archive-index .monthly .link', 'href', nameFormats[3], urlFormats[3]);
          }
        }
      }
    })(window);
  </script>
  <style>
    #<data:widget.instanceId/> .archive-navigation {
      margin: 1em 0 0 0;
    }
    #<data:widget.instanceId/> .page_list ul, #<data:widget.instanceId/> .page_list ol {
      margin: 16px;
    }
    #<data:widget.instanceId/> .page_list li {
      margin: 16px 0;
    }
    #<data:widget.instanceId/> .page_list button {
      margin: 0 16px 16px;
      min-width: 36px;
      min-height: 36px;
    }
    #<data:widget.instanceId/> .page_list button.prev {
      float: left;
      margin-left: 0;
    }
    #<data:widget.instanceId/> .page_list button.next {
      float: right;
      margin-right: 0;
    }
    #<data:widget.instanceId/> .tab_view .tabs {
      padding: 16px 0 4px 0;
      border-width: 0 0 1px 0;
      border-style: solid;
      border-color: #c0c0c0;
    }
    #<data:widget.instanceId/> .tab_view .tab_button {
      display: none;
    }
    #<data:widget.instanceId/> .tab_view .tab_button + label {
      margin: 4px;
      padding: 4px 4px;
      background: #f0f0f0;
      border: 1px solid #c0c0c0;
      border-radius: 4px 4px 0 0;
      color: #606060;
    }
    #<data:widget.instanceId/> .tab_view .tab_button:checked + label {
      background: #fff0e0;
      border-bottom-color: transparent;
      color: #404040;
    }
    #<data:widget.instanceId/> li.expanded {
      font-weight:bold;
    }
  </style>
</b:includable>
<b:includable id='interval' var='intervals'>
</b:includable>
<b:includable id='posts' var='posts'>
  <ul class='posts hierarchy'>
    <b:loop values='data:posts' var='post'>
      <li>
        <a expr:href='data:post.url'><data:post.title/></a>
      </li>
    </b:loop>
  </ul>
</b:includable>

なお、ガジェットの設定で [最も古い投稿を最初に表示する] を [OFF] にした場合、左が新しくて右が古い、上が新しくて下が古いというルールになります。

注意点

2022年2月28日現在の Blogger の様々な仕様(特に自動生成されるURLの構造や、アーカイブガジェットで取得可能な各種データなど)を活用していますので、Blogger 側で仕様変更が行われると表示が壊れる等の問題が発生する可能性があります。

また、この魔改造アーカイブガジェットは JavaScript を使用しています。 JavaScript が無効なブラウザでは正しく使用できません。

元に戻したい時

この魔改造ガジェットが気に食わない、きちんと動作しないなど、何らかの理由でもとに戻したい事があると思います。

そのときは、 [レイアウト] からアーカイブガジェットを削除してください。

デフォルトの状態に戻したい場合

アーカイブガジェットを削除してもう一度アーカイブガジェットを追加すればデフォルトの状態のアーカイブガジェットになりますし、以下の手順を実行することでもデフォルトの状態に戻ります。

[テーマ]→[HTMLを編集](カスタマイズの右の▼をクリックして選ぶ)→[ウィジェットをデフォルトに戻す]→アーカイブガジェット名だけを選択→[選択したガジェットを元に戻す]

導入を誤り、元に戻らなくなってしまった場合

アーカイブガジェットを削除したりしても元に戻らない状況になった場合は、導入の途中で誤ってアーカイブガジェットとは関係がない場所を削除・上書きしてしまった可能性があります。導入手順1でダウンロードしておいたバックアップから元に戻しましょう。バックアップのファイルがダウンロードフォルダにあると思います。

[テーマ]→[元に戻す](カスタマイズの右の▼をクリックして選ぶ)→[アップロード]→バックアップしたファイルを選択→[開く]

なお、バックアップしそびれた場合でデフォルトに戻すで効果がなかった場合、もう打つ手がありません。ご自分のお好みのテーマをもう一度導入し、元の状態に戻るようにカスタマイズし直してください。

仕組みについて

ガジェットのスタイルに合わせて、階層型とリスト型の二通りの実装をしています。

階層型

階層型アーカイブの画像。
アーカイブの設定にて、スタイルを「階層」にすると使用可能。
投稿の詳細ページでは、それが属するアーカイブ期間と、
その前後のアーカイブ期間、それと前後の投稿のタイトルを表示する。

階層型は3つの部分に分かれています。現在のアーカイブ区間を表示し、かつその前後への移動ができるナビゲーション部、目的のアーカイブにジャンプするためのメニュー部、それと現在の投稿とその前後の投稿を一度に最大5件まで表示する投稿リストです。プルダウンメニューでジャンプする仕組みと、投稿リストのページ切り替えの仕組みを実現するために JavaScript を使用しています。

これにより、現在見ているアーカイブが何年何月のアーカイブ区間かを気にすることなく、1クリックで前後のアーカイブ区間や前後の投稿に移動することができます。

なお、この表示形式は階層構造をリストで表現していませんが、プルダウンメニューが階層を表していますので、階層型アーカイブだということにしてください。

リスト型

リスト型アーカイブの画像。
スタイルでリストかプルダウンメニューにすると使用可能。
年別アーカイブと月別アーカイブのリストを切り替えることができ、
現在見ている投稿を含むアーカイブ期間とその前後の5件が表示される。

リスト型は年間アーカイブ、月間または週間アーカイブ、設定によっては日別アーカイブを含めた、最大3つのアーカイブ期間リストをタブで切り替える仕組みになっています。いずれのリストも一度に最大5件まで表示し、ボタンでページ切り替えを行います。タブ切り替え、ページ切り替え、それとリンクテキストとリンク先URLの改変には JavaScript を使用しています。

リンクテキストとリンク先URLの改変とは

リンクのテキストとURLを JavaScript で書き換えると書くと、人によっては不安に思うかもしれません。年間アーカイブのリストの作り方を例に、何のために必要かを説明します。

まず、この魔改造アーカイブガジェットを使用すると、Blogger のサーバーがブログのHTMLファイルを出力するとき、「各年のはじめのアーカイブページのリスト」を作ります。つまり、この段階ではまだ年間アーカイブのリストは完成していません。

次に、HTMLファイルを受け取ったウェブブラウザがテーマに組み込まれたスクリプトを起動します。すると該当箇所のリンク先のURLを解析し、「各年のはじめのアーカイブページのリスト」を「年間アーカイブのリスト」に作り変えます。

JavaScript の動作の具体例を挙げると、各年のはじめのアーカイブページのリストに「https://<ブログ名>/2020_01_13_archive.html」へのリンクが含まれていたら、その URL から 2020 年の年間アーカイブが実在すると判断し、「https://<ブログ名>/2020」という 2020 年の年間アーカイブの URL に変換します。そして、リンクのテキストも同様に「2020」に変更します。このような作業をリストの全項目に対して行うことで、年間アーカイブのリストに変換します。

ブログ内のパス部分だけを変更しますので、違うサイトに誘導するなどの悪質な挙動は致しません。

なお、なぜ最初から年間アーカイブのリストを作らないのかというと、魔改造アーカイブガジェットを公開した現時点では Blogger のサーバー側に細かな文字列操作の手段が用意されていないからです。年を表す4文字だけ取得したいといった操作ができないため、 JavaScript を併用するほかないためです。

最後に

こんな魔改造アーカイブガジェットを紹介しましたが、正直なところ、アーカイブガジェット自体は読者に使われることが少ないガジェットですので、需要があるのかなー、などと思いながら今まで作っていました。

しかし、特に階層型のアーカイブは、今までのアーカイブガジェットとは一風違う、元々のアーカイブガジェットの役割を超えたものを作ることができたかなと思います。「これを導入すればブログ内の導線が一つ増えるぞ!」と得意満面を浮かべながら、この投稿を書き上げました。

もしよければ、使っていただけると幸いです。


0 件のコメント :

コメントを投稿