& تقدم المرش المُرشح (وتاريخ إجراء الشنانيات) (العربية (Arabic))

& تقدم المرش المُرشح (وتاريخ إجراء الشنانيات)

Monday, 10 November 2025

//

8 minute read

أولاً

على مدى الأيام القليلة الماضية كنت أتجول بعيداً في شريط المرشّح الجديد للمدونة: اختيار اللغة، الطلب، ومختار مدى التاريخ الذي يحاول جاهداً جداً أن يكون ذكياً (أحياناً ذكياً جداً).

هذا العمود يمر من خلال ما بنيته، لماذا بضعة أشياء ذهبت جانبا (التاريخ لا يتصرف، والنقاط البارزة لا ينعش)، وكيف قمت بتثبيتها.

الكثير من الشفرة المصدرية و زوجين من رسومات حورية البحر لإظهار التدفق.

تذكّرْ هذا الموقعِ a عمل في التقدمِ، هذا النوعِ مِنْ المادةِ سَيَحْدثُ عندما تَأْكلُ غذاءَ كلبِكَ الخاصِ!

  • ما الذي تغير؟
  • هذا هو المستوى العالي: لقد أضفت شريط ترشيح مناسب إلى فهرس المدونات الذي يتضمن:
  • انت انت انت انت انت من حفظ مرشحاتك الحالية و طلب الطلب لقائمة عن طريق HTMx
  • اختيار ترتيب ترتيب الصنف الذي يحفظ السياق أيضاً (التاريخ/العنوان: ec/desc/desc)
  • ملتقط نطاق تاريخ (Flatesker في نمط المدى) مع خادم اليوم المُسْطَر عليه

حُفِل حالة العنوان دائماً في مزامن (عِد/ إلى الأمام، عمل روابط عميقة)

منفذ مُسند جاري حالياً في هذا الحال

على طول الطريق كان لي بضعة حشرات: تاريخ النطاقات التي تضيع على تغيير اللغة/الطلب، يسلط التقويم الضوء على عدم المنعشة في المبادلة، و واحد ممتع حيث أساليب النمط المظلمة جعلت التقويم يبدو مكسورا لأن فلاتيكر لم يكن إعادة السحب.

<div id="filters" hx-target="#content">
  <select id="languageSelect">…</select>
  <select id="orderSelect">
    <option value="date_desc">Newest first</option>
    <option value="date_asc">Oldest first</option>
    <option value="title_asc">Title A–Z</option>
    <option value="title_desc">Title Z–A</option>
  </select>
  <input id="dateRange" type="text" placeholder="YYYY-MM-DD → YYYY-MM-DD" />
  <button id="clearDateFilter">Clear</button>
  <div id="filterSummary"></div>
</div>

<div id="content"><!-- HTMX swaps blog list here --></div>

& مُرشح في DOM

هذا هو الـ خام بنية نص:Mostlylucid/src/js/blog-index.jsالعميل: HTMX + أسلاك مسطحة

  • من السلوك يعيش في
function applyNavigation(u){
  const target = document.querySelector('#content');
  try{ window.history.pushState({}, '', u.toString()); }catch{}
  if(window.htmx && target){
    window.htmx.ajax('GET', u.toString(), {
      target: '#content',
      swap: 'outerHTML show:none',
      headers: {'pagerequest': 'true'}
    });
  } else {
    window.location.href = u.toString();
  }
}
const url = new URL(window.location.href);
const existingStart = url.searchParams.get('startDate');
const existingEnd   = url.searchParams.get('endDate');
const existingLang  = url.searchParams.get('language') || 'en';
const existingOrderBy = (url.searchParams.get('orderBy') || 'date').toLowerCase();
const existingOrderDir = (url.searchParams.get('orderDir') || 'desc').toLowerCase();

langSelect.value = existingLang;
orderSelect.value = `${existingOrderBy}_${existingOrderDir}`;
updateSummary();
  • عدد قليل من القطع المهمة:
async function fetchMonth(year, month, language){
  const res = await fetch(`/blog/calendar-days?year=${year}&month=${month}&language=${encodeURIComponent(language||'en')}`);
  if(!res.ok) return new Set();
  const j = await res.json();
  return new Set(j.dates || []);
}

function formatYMD(d){ return d.toISOString().substring(0,10); }

let highlightDates = new Set();
const fp = window.flatpickr(input, {
  mode: 'range',
  dateFormat: 'Y-m-d',
  defaultDate: [existingStart, existingEnd].filter(Boolean),
  onDayCreate: function(_dObj,_dStr,fpInstance,dayElem){
    const ymd = formatYMD(dayElem.dateObj);
    if(highlightDates.has(ymd)){
      dayElem.classList.add('has-post');
      dayElem.style.background = 'rgba(76,175,80,0.35)';
      dayElem.style.borderRadius = '6px';
    }
  },
  onMonthChange: async function(_sd,_ds,fpInstance){
    highlightDates = await fetchMonth(fpInstance.currentYear, fpInstance.currentMonth+1, langSelect.value);
    fpInstance.redraw();
  },
  onOpen: async function(_sd,_ds,fpInstance){
    highlightDates = await fetchMonth(fpInstance.currentYear, fpInstance.currentMonth+1, langSelect.value);
    fpInstance.redraw();
  },
  onChange: function(selectedDates){
    if(selectedDates.length === 2){
      const [start,end] = selectedDates;
      const u = new URL(window.location.href);
      u.searchParams.set('startDate', formatYMD(start));
      u.searchParams.set('endDate', formatYMD(end));
      u.searchParams.set('page','1');
      u.searchParams.set('language', langSelect.value);
      const [ob,od] = orderSelect.value.split('_');
      u.searchParams.set('orderBy', ob);
      u.searchParams.set('orderDir', od);
      updateSummary();
      applyNavigation(u);
    }
  }
});
  • المحافظة على الموقع وملاحة HTMX
langSelect.addEventListener('change', async function(){
  const u = new URL(window.location.href);
  u.searchParams.set('language', langSelect.value);
  u.searchParams.set('page', '1');
  const [ob,od] = (orderSelect.value||'date_desc').split('_');
  u.searchParams.set('orderBy', ob);
  u.searchParams.set('orderDir', od);
  if(input._flatpickr && input._flatpickr.selectedDates.length===2){
    const [s,e] = input._flatpickr.selectedDates;
    u.searchParams.set('startDate', formatYMD(s));
    u.searchParams.set('endDate', formatYMD(e));
  }
  // refresh calendar highlights for new language
  if(input._flatpickr){
    const fp = input._flatpickr;
    highlightDates = await fetchMonth(fp.currentYear, fp.currentMonth+1, langSelect.value);
    fp.redraw();
  }
  updateSummary();
  applyNavigation(u);
});
  • تثبيت ، افتراض من العنوان ، و حفظ المزامن في المزامن من الملخص unI
const obs = new MutationObserver(() => {
  const dr = root.querySelector('#dateRange');
  const fp = dr && dr._flatpickr;
  if(fp && typeof fp.redraw === 'function') fp.redraw();
});
obs.observe(document.documentElement, { attributes:true, attributeFilter:['class'] });

حد نطاق مَر مع خادم اليوم المُسْرِز

التواريخ التي ينبغي فيها تغيير اللغة أو الترتيب

  • إعادة سحب التقويم عندما يقوم الموضوع بالتبديلات (الضوء/ المصباح) بحيث يعاد الطلاءات*
  • المصدر: نقاط النهاية والكافضcalendar-days2 نقطة نهاية سُلطة الصفحة:
// GET /blog
[HttpGet]
[ResponseCache(Duration = 300, VaryByHeader = "hx-request",
  VaryByQueryKeys = new[] { "page", "pageSize", nameof(startDate), nameof(endDate), nameof(language), nameof(orderBy), nameof(orderDir) },
  Location = ResponseCacheLocation.Any)]
[OutputCache(Duration = 3600, VaryByHeaderNames = new[] { "hx-request" },
  VaryByQueryKeys = new[] { nameof(page), nameof(pageSize), nameof(startDate), nameof(endDate), nameof(language), nameof(orderBy), nameof(orderDir) })]
public async Task<IActionResult> Index(int page = 1, int pageSize = 20, DateTime? startDate = null, DateTime? endDate = null,
  string language = MarkdownBaseService.EnglishLanguage, string orderBy = "date", string orderDir = "desc")
{
    var posts = await blogViewService.GetPagedPosts(page, pageSize, language: language, startDate: startDate, endDate: endDate);
    posts.LinkUrl = Url.Action("Index", "Blog", new { startDate, endDate, language, orderBy, orderDir });
    if (Request.IsHtmx()) return PartialView("_BlogSummaryList", posts);
    return View("Index", posts);
}

// GET /blog/calendar-days?year=2025&month=11&language=en
[HttpGet("calendar-days")]
[ResponseCache(Duration = 300, VaryByHeader = "hx-request", VaryByQueryKeys = new[] { nameof(year), nameof(month), nameof(language) }, Location = ResponseCacheLocation.Any)]
[OutputCache(Duration = 1800, VaryByHeaderNames = new[] { "hx-request" }, VaryByQueryKeys = new[] { nameof(year), nameof(month), nameof(language) })]
public async Task<IActionResult> CalendarDays(int year, int month, string language = MarkdownBaseService.EnglishLanguage)
{
    if (year < 2000 || month < 1 || month > 12) return BadRequest("Invalid year or month");
    var start = new DateTime(year, month, 1);
    var end = start.AddMonths(1).AddDays(-1);
    var posts = await blogViewService.GetPostsForRange(start, end, language: language);
    if (posts is null) return Json("");
    var dates = posts.Select(p => p.PublishedDate.Date).Distinct().OrderBy(d => d).Select(d => d.ToString("yyyy-MM-dd")).ToList();
    return Json(new { dates });
}

الفهرس نفسه، الذي يقبل الصفحة، وصفحة النظامSiz، وبداية Date/end Date، واللغة، وترتيبLinkUrlA A

public class BasePagingModel<T> : Interfaces.IPagingModel<T> where T : class
{
    public int Page { get; set; }
    public int TotalItems { get; set; } = 0;
    public int PageSize { get; set; }
    public ViewType ViewType { get; set; } = ViewType.TailwindAndDaisy;
    public string LinkUrl { get; set; }
    public List<T> Data { get; set; }
}

نقطة النهاية التي ترجع مجموعة الأيام في شهر معين التي لها وظائف (للنقاط البارزة)

sequenceDiagram
  participant U as User
  participant FP as Flatpickr
  participant JS as blog-index.js
  participant HT as HTMX
  participant C as BlogController

  U->>FP: Selects 2025-11-01 → 2025-11-10
  FP-->>JS: onChange([start,end])
  JS->>JS: Update URLSearchParams (startDate, endDate, language, order)
  JS->>HT: htmx.ajax('GET', /blog?...)
  HT->>C: GET /blog with query
  C-->>HT: Partial _BlogSummaryList
  HT-->>U: Swap #content
  JS->>FP: (after swap) ensure highlights + redraw

وحيّاً جانباً: نموذج الندب

flowchart TD
  A[Open calendar / Month change] --> B[Fetch /blog/calendar-days]
  B -->|JSON dates: yyyy-mm-dd array| C[Set highlightDates]
  C --> D[flatpickr redraw]
  D --> E[onDayCreate adds .has-post]

إذاً فإن الترجيح يحافظ على سياق المرشّح الحالي عند تقديم الخادم إلى جانب.

  • التبعية: كيفية تغيير تدفقات المرش

    • الجدول البياني:startDate/endDate.
    • المحاقن التي ضربتها (والمعالجات)new URL(window.location.href)التاريخ المفقود في اللغة/تغيير الطلبlanguage/orderالسبب: بنيت العنوان الجديد من الصفر، وليس من الحالي؛ فقد
  • دائما ابدأ مع

    • وفقط تغيير الأجزاء التي تغيرت؛ أو قراءة التواريخ من Flastecer إذا كان حاضرا.

  • تغيير المعالجات أعلى.

    • لم يُنعش مُبرزات التقويمات بعد مبادلة HTMX
    • سبب الوفاة: عاشت حالة الإقتياد على DOM القديمة وماتت على المبادلة.<html class>في الداخل، دمّر أيّ حالة حاليّة، إعادة خلق، ثمّ حمّل الملامح البارزة للشهر المرئيّ.fp.redraw().
  • كما أُعيد ترتيبها بعد المبادلات (انظر أدناه).

    • لم يُمضLinkUrlالسبب : تغيَّرت اصناف الاسلوب ولكن لم تُعِد اياما مرسومة .
    • وضع في وضع:posts.LinkUrl = Url.Action("Index","Blog", new { startDate, endDate, language, orderBy, orderDir })التغييرات والنداءات

السبب:

(function(){
  function initFromRoot(root){ /* …the big function shown above… */ }
  if(window.htmx){
    document.body.addEventListener('htmx:afterSwap', function(ev){
      const tgt = ev.detail.target;
      // re-init if the content swapped includes the filters/content area
      if(tgt && (tgt.id === 'content' || tgt.querySelector?.('#filters'))){
        initFromRoot(document);
      }
    });
  }
  // also run on first load
  initFromRoot(document);
})();

ولم يكن من المقرر إدراج هذا الاستعلام في بعض الحالات.

  • في الخادم، وُضِع
  • لذا فإنّ المُلْحَف يُؤَمِّنُ URLs بشكل صحيح.
  • السلامة في المبادلة: يُذكر عند الطلب

إذا كنت تستخدم المبادلات الجزئية، يجب أن تكون البورصة الخاصة بك قادرة على ذلك.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.