预览

年月历文章归档卡片

相比自带卡片占用更小的展示面积,能查看所有月份的文章数量,点击年份可以查看对应年份的所有文章,左右两个按钮可以切换年份。

实现

  1. 找到\themes\butterfly\scripts\helpers\aside_archives.js,注释掉所有原有的代码,替换成下面代码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    'use strict'
    hexo.extend.helper.register('aside_archives', function (options = {}) {
    const { config } = this
    const archiveDir = config.archive_dir
    const { timezone } = config
    const lang = toMomentLocale(this.page.lang || this.page.language || config.language)
    let { format } = options
    const type = options.type || 'monthly'
    const order = options.order || -1
    const compareFunc = type === 'monthly'
    ? (yearA, monthA, yearB, monthB) => yearA === yearB && monthA === monthB
    : (yearA, monthA, yearB, monthB) => yearA === yearB
    let result = ''
    if (!format) {
    format = type === 'monthly' ? 'MMMM YYYY' : 'YYYY'
    }
    const posts = this.site.posts.sort('date', order)
    if (!posts.length) return result
    const data = []
    let length = 0
    posts.forEach(post => {
    // Clone the date object to avoid pollution
    let date = post.date.clone()
    if (timezone) date = date.tz(timezone)
    const year = date.year()
    const month = date.month() + 1
    const lastData = data[length - 1]
    if (!lastData || !compareFunc(lastData.year, lastData.month, year, month)) {
    if (lang) date = date.locale(lang)
    const name = date.format(format)
    length = data.push({
    name,
    year,
    month,
    count: 1
    })
    } else {
    lastData.count++
    }
    })
    // 按年份分组
    const groupedData = data.reduce((acc, { name, year, month, count }) => {
    if (!acc[year]) {
    acc[year] = { year, sum: 0, data: [] }; // 初始化年份条目
    }
    acc[year].sum += count; // 累加年份的总文章数
    acc[year].data.push({ name, month, count }); // 将月份数据加入对应年份
    return acc;
    }, {});
    // 转换为目标数组格式
    const newData = Object.values(groupedData);
    result += `<div class="item-headline"><i class="fas fa-archive"></i><span>${this._p('aside.card_archives')}</span>`
    result += '</div>'
    result += `<div class="card-archive-calendar">
    <div class="card-archive-calendar-head">
    <button class="card-archive-calendar-left`
    if (newData.length === 1) result += ' no-event'
    result +=
    `" onclick="ctrl.card_archive_calendar_prev()">
    <i class="fas fa-angle-left"></i>
    </button>
    <div class="card-archive-calendar-center">
    <div class="card-archive-calendar-years" style="transform: translateX(0px);">`
    for (let i = 0; i < newData.length; i++) {
    let url = `${archiveDir}/${newData[i].year}/`
    result +=
    `<a class="card-archive-calendar-year-item" href="${this.url_for(url)}">
    <span class="card-archive-calendar-year">${newData[i].year}</span>
    <span class="card-archive-calendar-year-count">[${newData[i].sum}]</span>
    </a>`
    }
    result +=
    `</div>
    </div>
    <button class="card-archive-calendar-right no-event" onclick="ctrl.card_archive_calendar_next()">
    <i class="fas fa-angle-right"></i>
    </button>
    </div>
    <div class="card-archive-calendar-body">
    <div class="card-archive-calendar-pages" style="transform: translateX(0px);">`
    for (let i = 0; i < newData.length; i++) {
    let item = newData[i]
    let m = ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
    let k = item.data.length - 1
    result +=
    `<div class="card-archive-calendar-page">`
    for (let j = 0; j < 12; j++) {
    let c = 0
    if (k >= 0 && j == item.data[k].month - 1) {
    c = item.data[k].count
    let url = `${archiveDir}/${item.year}/`
    if (type === 'monthly') {
    if (item.data[k].month < 10) url += '0'
    url += `${item.data[k].month}/`
    }
    result +=
    `<a class="card-archive-calendar-month-item" href="${this.url_for(url)}">`
    k--
    } else {
    result +=
    `<a class="card-archive-calendar-month-item no-link">`
    }
    result +=
    `<span class="card-archive-calendar-month">${m[j]}</span>
    <span class="card-archive-calendar-month-count">${c}篇</span>
    </a>`
    }
    result +=
    `</div>`
    }
    result +=
    `</div>
    </div>
    </div>`
    return result
    })
    const toMomentLocale = function (lang) {
    if (lang === undefined) {
    return undefined
    }
    // moment.locale('') equals moment.locale('en')
    // moment.locale(null) equals moment.locale('en')
    if (!lang || lang === 'en' || lang === 'default') {
    return 'en'
    }
    return lang.toLowerCase().replace('_', '-')
    }
  2. 补充逻辑函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    var ctrl = {
    card_archive_calendar_prev() {
    let a = document.querySelector(".card-archive-calendar-years")
    let b = document.querySelector(".card-archive-calendar-pages")
    let count = a.childElementCount
    let prev = document.querySelector(".card-archive-calendar-left")
    let next = document.querySelector(".card-archive-calendar-right")
    let t = a.style.transform
    if (t) {
    let m = parseFloat(t.match(/translateX\((-?\d+\.?\d*)(px|%)?\)/)[1])
    let n = m / 100
    if (n > 0 && n < count - 2) {
    a.style.transform = "translateX(" + 100 * (n+1) + "px)"
    b.style.transform = "translateX(" + 298 * (n+1) + "px)"
    } else if (n == count - 2) {
    a.style.transform = "translateX(" + 100 * (n+1) + "px)"
    b.style.transform = "translateX(" + 298 * (n+1) + "px)"
    prev.classList.add("no-event")
    } else if (n == 0) {
    a.style.transform = "translateX(" + 100 + "px)"
    b.style.transform = "translateX(" + 298 + "px)"
    if (count == 2) {
    prev.classList.add("no-event")
    next.classList.remove("no-event")
    } else if (count > 2) {
    next.classList.remove("no-event")
    }
    }
    }
    },

    card_archive_calendar_next() {
    let a = document.querySelector(".card-archive-calendar-years")
    let b = document.querySelector(".card-archive-calendar-pages")
    let count = a.childElementCount
    let prev = document.querySelector(".card-archive-calendar-left")
    let next = document.querySelector(".card-archive-calendar-right")
    let t = a.style.transform
    if (t) {
    let m = parseFloat(t.match(/translateX\((-?\d+\.?\d*)(px|%)?\)/)[1])
    let n = m / 100
    if (n > 1 && n < count - 1) {
    a.style.transform = "translateX(" + 100 * (n-1) + "px)"
    b.style.transform = "translateX(" + 298 * (n-1) + "px)"
    } else if (n == count - 1) {
    a.style.transform = "translateX(" + 100 * (n-1) + "px)"
    b.style.transform = "translateX(" + 298 * (n-1) + "px)"
    prev.classList.remove("no-event")
    } else if (n == 1) {
    a.style.transform = "translateX(0px)"
    b.style.transform = "translateX(0px)"
    next.classList.add("no-event")
    }
    }
    }
    }
  3. 添加样式代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    .card-archive-calendar-head {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 5px;
    }

    .card-archive-calendar-head button {
    height: 30px;
    width: 30px;
    color: var(--font-color);
    transition: .3s;
    }

    .card-archive-calendar-head button,
    .card-archive-calendar-center {
    background: var(--gavin-widget-bg1);
    border-radius: 8px;
    }

    .card-archive-calendar-head button:hover,
    .card-archive-calendar-year-item:hover {
    background: var(--gavin-widget-bg2);
    color: var(--dis-f-0);
    }

    .card-archive-calendar-year-item span,
    .card-archive-calendar-month-item span {
    line-height: 1.3;
    }

    .card-archive-calendar-center {
    height: 30px;
    width: 100px;
    display: flex;
    overflow: hidden;
    justify-content: flex-end;
    }

    .card-archive-calendar-years {
    display: flex;
    flex-direction: row;
    transition: .3s ease;
    }

    .card-archive-calendar-year-item {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100px;
    }

    .card-archive-calendar-year {
    font-weight: bold;
    }

    .card-archive-calendar-year-count {
    margin-left: 5px;
    }

    .card-archive-calendar-year-count,
    .card-archive-calendar-month-count {
    font-size: 85%;
    font-weight: bold;
    opacity: .4;
    }

    .card-archive-calendar-body {
    height: 100%;
    width: 100%;
    display: flex;
    overflow: hidden;
    justify-content: flex-end;
    padding: 5px 0;
    }

    .card-archive-calendar-pages {
    display: flex;
    flex-direction: row;
    transition: .3s ease;
    }

    .card-archive-calendar-page {
    height: 100%;
    width: calc(298px - 10px);
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0 5px;
    }

    .card-archive-calendar-month-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    width: calc((100% - 30px) / 4);
    height: 50px;
    cursor: pointer;
    background: var(--gavin-widget-bg1);
    border-radius: 8px;
    font-weight: bold;
    }

    .card-archive-calendar-month-item:hover {
    background: var(--gavin-nav-hover);
    color: #fff;
    }

    .card-archive-calendar-head button.no-event,
    .card-archive-calendar-month-item.no-link {
    opacity: 0.3;
    pointer-events: none;
    }