Home أخبار عناوين التاريخ: دع جرس العدالة يدق!

عناوين التاريخ: دع جرس العدالة يدق!

4
0

حظر في أي مكان على الصفحة * (الرأس، النص، فتحة “HTML المخصصة لـ CMS، وما إلى ذلك) – ينتظر DOM إذا لزم الأمر * ولن يتسرب أي عالمي. * * خيارات قابلة للتكوين (تحرير كائن CONFIG أدناه): * – محدد CSS لحاويات المقالة لطيها. * – جزء مطوي من الارتفاع (0-1) من ارتفاع المحتوى الكامل لإظهار * قبل الانهيار، على سبيل المثال 0.5 = إظهار أعلى 50%. * – ارتفاع التدرج (px) للتدرج اللوني المتراكب على * الجزء السفلي من المحتوى المطوي * – لون متدرج يتلاشى التدرج اللوني بشكل افتراضي إلى اللون الأبيض – * إذا لم تكن خلفية الصفحة بيضاء، قم بتغيير هذا * أو سيبدو التلاشي كمربع/حافة مرئية * بدلاً من التلاشي السلس في الصفحة. * – حد أمان minExpandRatio: إذا كان الجزء المخفي من مقالة * (الارتفاع الكامل مطروحًا منه الارتفاع المطوي) * أصغر من (gradientHeight * minExpandRatio)، * يتم تركه بمفرده (بدون طي، بدون زر) نظرًا لعدم وجود محتوى مخفي كافٍ لتبريره. * – minFullHeight الحد الأدنى لارتفاع المحتوى الكامل (px) الذي يجب أن تتوفر فيه المقالة قبل طيها على الإطلاق. المقالات * الأقصر من هذا تترك وحدها، بدون زر. * – التسمية الرئيسية لنص الزر الموضحة على زر التوسيع. * – ButtonSubText السطر الثاني من النص يظهر أسفل التسمية الرئيسية * (أصغر، 16 بكسل). اضبط على ” لحذفه تمامًا. * – TransitionDurationMs مدة الرسوم المتحركة للارتفاع/التعتيم. * ==========================================================================*/ (function () { ‘use strict’; var CONFIG = { selector: ‘.asset-body’, collapsedHeightPercent: 0.5، gradientHeight: 120، gradientColor: ‘#ffffff’، minExpandRatio: 1.5، minFullHeight: 800، ButtonText: ‘اقرأ المزيد’، ButtonSubText: ‘انقر هنا لمواصلة القراءة’، TransitionDurationMs: 400، // نسبة العرض إلى الارتفاع لإطار الوسائط داخل “.editor-tv-wrapper” // مشغل الفيديو (16:9). يكون عرضه مستقرًا بمجرد تخطيط الصفحة، // لذلك يتنبأ هذا بالارتفاع النهائي للإطار في المقدمة بدلاً من التفاعل بعد تحميله. tvAspectRatio: 16 / 9, // تمت إضافة مخزن مؤقت إضافي (px) أعلى ارتفاع الغلاف المتوقع، فقط // في حالة انخفاض نسبة العرض إلى الارتفاع قليلاً tvExtraBuffer: 20 }; (document.querySelector(‘style[data-read-more-styles]’)) يعود؛ فار المغلق = [
‘.read-more-container {‘,
‘ position: relative;’,
‘ overflow: hidden;’,
‘ transition: height var(–read-more-transition-duration, 400ms) ease;’,
‘}’,
‘.read-more-overlay {‘,
‘ position: absolute;’,
‘ left: 0;’,
‘ right: 0;’,
‘ bottom: 0;’,
‘ height: var(–read-more-gradient-height, 120px);’,
‘ display: flex;’,
‘ align-items: flex-end;’,
‘ justify-content: center;’,
‘ transition: opacity var(–read-more-transition-duration, 400ms) ease;’,
‘}’,
‘.read-more-overlay::before {‘,
‘ content: “”;’,
‘ position: absolute;’,
‘ inset: 0;’,
‘ background: linear-gradient(to bottom, rgba(0,0,0,0) 0%, var(–read-more-gradient-color, #ffffff) 85%);’,
‘ pointer-events: none;’,
‘}’,
‘.read-more-overlay–hidden {‘,
‘ opacity: 0;’,
‘ pointer-events: none;’,
‘}’,
‘.read-more-btn {‘,
‘ position: relative;’,
‘ z-index: 100;’,
‘ margin-bottom: 14px;’,
‘ padding: 20px 34px;’,
‘ font-size: 18px;’,
‘ font-family: “PT Sans”, sans-serif;’,
‘ line-height: 1.2;’,
‘ color: #eeeeee;’,
‘ background: linear-gradient(150deg, #1e3a8a 0%, #2563eb 60%, #1e3a8a 100%);’,
‘ background-size: 200% 200%;’,
‘ background-position: 0% 50%;’,
‘ border: none;’,
‘ border-radius: 8px;’,
‘ cursor: pointer;’,
‘ pointer-events: auto;’,
‘ width: 60%;’,
‘ font-weight: bold;’,
‘ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);’,
‘ transition: background-position 0.5s ease, transform 0.2s ease, box-shadow 0.2s ease;’,
‘}’,
‘.read-more-btn:hover {‘,
‘ background-position: 100% 50%;’,
‘ color: white;’,
‘ transform: translateY(-2px);’,
‘ box-shadow: 0 5px 14px rgba(0, 0, 0, 0.35);’,
‘}’,
‘.read-more-btn-label {‘,
‘ display: block;’,
‘}’,
‘.read-more-btn-subtext {‘,
‘ display: block;’,
‘ font-size: 16px;’,
‘ margin-top: 4px;’,
‘ color: #eee;’,
‘ font-weight: normal;’,
‘}’
].join(‘n’); فار ستايل = document.createElement(‘style’); style.setAttribute(‘data-read-more-styles’, ”); style.textContent = css; document.head.appendChild(style); } وظيفة findAdjacentAdSlots(container) { var assetBelow = findAssetBelow(container); if (!assetBelow) return { revcontent: null, empLocal: null }; var empMatch = assetBelow.querySelector(‘#tncms-block-1733607’); فار revMatch = assetBelow.querySelector(‘[data-widget-host=”revcontent”][data-widget-id=”287337″]’); // في القالب المباشر، يتم عرض كلا الواجهتين داخل غلاف CMS الخاص بهما // “.tncms-block”، وهو مستوى واحد أسفل العنصر المحدد لدينا // يتطابق فعليًا – إعادة ترتيب العنصر المطابق نفسه يؤدي فقط إلى تضمينه // داخل غلاف عنصر واجهة المستخدم الآخر بدلاً من نقله فعليًا إلى الأعلى. // الحل إلى هذا الغلاف (الرجوع إلى العنصر المطابق نفسه // عندما لا يكون هناك عنصر واحد، كما هو الحال في صفحة الاختبار العادية) لذلك يحدث المبادلة // في المكان الصحيح مستوى الأخوة var empLocal = empMatch ? (empMatch. Closest(‘.tncms-block’) || empMatch): null; } function moveEmpLocalAboveRevcontent(slots) { if (slots.revcontent && Slots.empLocal && Slots.empLocal.nextElementSibling !== Slots.revcontent) { Slots.revcontent.parentNode.insertBefore(slots.empLocal, Slots.revcontent) } } // Revcontent and EMP Local عبارة عن عناصر نائبة لعناصر واجهة المستخدم التي غالبًا لا تحتوي على // محتواها الحقيقي (وأحيانًا لا تكون موجودة في DOM على الإطلاق) في // لحظة تشغيل هذا البرنامج النصي، نظرًا لأنه يتم ملؤها بواسطة نصوص // المحمل غير المتزامنة الخاصة بها بعد عرض الصفحة الأولية – وبعض أدوات الإعلانات // (بما في ذلك محتوى Revcontent) تنقل ترميزها الخاص إلى مكان آخر في // DOM بمجرد الانتهاء من التحميل، مما قد يؤدي بصمت إلى التراجع عن إعادة ترتيب لمرة واحدة. // لذا راقب #asset-below لمعرفة التغييرات واستمر في إعادة تنفيذ الأمر // كلما تغير أي شيء، بدلاً من التوقف بعد النجاح الأول. وظيفة جدولةEmpLocalReorder(container) { var assetBelow = findAssetBelow(container); if (!assetBelow) return; دالة EnforceOrder() { moveEmpLocalAboveRevcontent(findAdjacentAdSlots(container)); } EnforceOrder(); var Observer = new MutationObserver(enforceOrder); Observer.observe(assetBelow, { قائمة الطفل: صحيح، الشجرة الفرعية: صحيح }); // شبكة الأمان: توقف عن المشاهدة بعد فترة حتى لا يجلس المراقب // هناك إلى أجل غير مسمى على الصفحات التي لا يتم تحميل الأداة فيها (أو تكون مفقودة). window.setTimeout(function () { Observer.disconnect(); }, 30000); } function findAssetBelow(container) { // #asset-below ليس دائمًا شقيقًا مباشرًا لحاوية المقالة – // يمكن أن تقع أقسام div المجمعة للقالب (علامة العمود/الصف) بينهما، لذا قم بالمشي // عبر الأسلاف، والتحقق من الأشقاء اللاحقين لكل مستوى بدوره. (sibling.id === ‘asset-below’) return sibling; []; var els = assetBelow.querySelectorAll(‘.asset-author, .asset-tags’); els.forEach(function (el) { el.style.display = ‘none’; }); العودة إلس. } وظيفة تكشفAuthorAndTags(els) { els.forEach(function (el) { el.style.display = ”; }); } // يعرض فيديو “.editor-tv-wrapper” المضمن الكروم الخاص به (شريط العنوان، // الحدود) على الفور، ولكن يتم تحميل إطار الوسائط في المنتصف في // بعد ذلك – أحيانًا أقل من ثانية، وأحيانًا بعد عدة ثوانٍ. // بدلاً من التفاعل بعد الحقيقة (هش: مراقبو تغيير الحجم/الطفرة // لا يتم تسليمهم بشكل موثوق في كل حالة متصفح/علامة تبويب، ويضيف الاستقصاء // الحمل المستمر)، توقع الارتفاع النهائي للإطار مقدمًا من عرض الغلاف // (مستقر في وقت التخطيط، قبل عرض الإطار نفسه) ونسبة العرض إلى الارتفاع الثابتة، وأضف ذلك فوق أي ارتفاع // يشغله الغلاف (بما في ذلك الكروم) – بالإضافة إلى مخزن أمان مؤقت صغير – بدلاً من افتراض أن ارتفاع الإطار وحده هو الارتفاع النهائي الكامل للغلاف // function توقعTvWrapperHeight(el) { var rect = el.getBoundingClientRect(); PredictedFrameHeight = rect.width / CONFIG.tvAspectRatio; return rect.height + PredictedFrameHeight + CONFIG.tvExtraBuffer; } // إجمالي الارتفاع الإضافي الذي ستكتسبه الحاوية مرة واحدة في كل “.editor-tv-wrapper” // داخلها ينمو من حجمها الحالي (ربما العنصر النائب) إلى حجمها النهائي المتوقع تعتمد الرياضيات على الارتفاع النهائي الحقيقي للمقالة، وليس على ارتفاع التحميل المسبق functionتنبأTvWrapperGrowth(container) { var Growth = 0; Growth; } // إذا كان خط الانهيار المقترح يقع في منتصف // “.editor-tv-wrapper” – أو حتى قريب بدرجة كافية بحيث يقع شريط التلاشي/الزر // الموجود في الجزء السفلي من المنطقة المطوية فوقه – فادفع // نقطة الانهيار للأسفل بعيدًا بما يكفي بحيث يصل الغلاف بالكامل (عند الارتفاع النهائي المتوقع) ونطاق التدرج/الزر أسفله، ولا يتداخلان أبدًا // الارتفاع الكامل المتوقع (انظر توقعTvWrapperGrowth)، يُستخدم لوضع حد أقصى للنتيجة، لذلك لا يتم اقتراحه أبدًا بعد النهاية النهائية للمقال Container.getBoundingClientRect().top; var Adjust = height; Wrappers.forEach(function (el) { var rect = el.getBoundingClientRect(); var elBottom = (rect.top – ContainerTop) + PredictTvWrapperHeight(el); var minHeight = Math.ceil(elBottom + CONFIG.gradientHeight); if (minHeight > Adjust) { Adjust = minHeight; } }); // لا تقترح أبدًا نقطة انهيار تتجاوز الارتفاع الكامل الفعلي للمقال – // إذا كان الغلاف قريبًا بدرجة كافية من النهاية بحيث يتجاوز مسحه بالإضافة إلى نطاق التدرج // ذلك، فلن يتبقى شيء ذو معنى // للإخفاء على أي حال؛ دع الشيكات العادية “التي لا تستحق الانهيار” تتعامل معها. return Math.min(adjusted, fullHeight); } functionتوسيع(container, overlay, fullHeight) { Container.style.height = fullHeight + ‘px’; overlay.classList.add(‘read-more-overlay–hidden’); var onTransitionEnd = function (e) { if (e.target !== Container || e.propertyName !== ‘height’) return; حاوية.style.height = ‘تلقائي’؛ حاوية.style.overflow = ‘مرئي’؛ حاوية.removeEventListener(‘transitionend’, onTransitionEnd); }; حاوية.addEventListener(‘transitionend’, onTransitionEnd); // السلوك الافتراضي: قم بإزالة التراكب (والزر الخاص به) بمجرد انتهاء التلاشي //، وبالتالي يختفي الزر بعد التوسيع. لتحويل هذا إلى تبديل // “قراءة أقل” بدلاً من ذلك، استبدل هذه الإزالة بالمنطق الذي يقوم // بإعادة طي الحاوية مرة أخرى إلى ارتفاعها المطوي وتبديل تسمية/معالج الزر //، بدلاً من حذف التراكب. window.setTimeout(function () { if (overlay.parentNode) overlay.parentNode.removeChild(overlay); }, CONFIG.transitionDurationMs); } function setUpReadMore(container) { if (container.getAttribute(‘data-read-more-init’) === ‘true’) return; Container.setAttribute(‘data-read-more-init’, ‘true’); var fullHeight = Container.scrollHeight + PredictTvWrapperGrowth(container); varcollapsedHeight = Math.round(fullHeight * CONFIG.collapsedHeightPercent); lapsedHeight =voidCuttingTvWrapper(container,lapsedHeight,fullHeight); var HiddenHeight = fullHeight -lapsedHeight; var AuthorTagEls = HideAuthorAndTags(container); // لا يوجد محتوى مخفي كافٍ لإزعاج الانهيار: إذا كان الجزء الذي // سيتم إزالته أصغر من ~ 1.5x من التدرج نفسه، فإن // التلاشي سوف يأكل فقط في نهاية النص مع بالكاد // أي شيء ذي معنى مخفي تحته، لذا اترك المقالة بمفردها. if (hiddenHeight < CONFIG.gradientHeight * CONFIG.minExpandRatio) {schedulEmpLocalReorder(container); كشفAuthorAndTags(authorTagEls); يعود؛ } // قم أيضًا بتخطي المقالات القصيرة تمامًا، بغض النظر عن النسبة المذكورة أعلاه. if (fullHeight < CONFIG.minFullHeight) {schedulEmpLocalReorder(container); كشفAuthorAndTags(authorTagEls); يعود؛ } if (window.getComputedStyle(container).position === 'static') { Container.style.position = 'relative'; } Container.classList.add('read-more-container'); حاوية.style.setProperty('–قراءة المزيد-الانتقال-مدة', CONFIG.transitionDurationMs + 'ms'); حاوية.style.overflow = 'مخفي'؛ Container.style.height =lapsedHeight+'px'; فار تراكب = document.createElement('div'); overlay.className = 'قراءة المزيد-تراكب'; overlay.style.setProperty('-قراءة-المزيد-متدرج-الارتفاع', CONFIG.gradientHeight + 'px'); overlay.style.setProperty('–قراءة المزيد-التدرج اللون', CONFIG.gradientColor); زر فار = document.createElement('button'); Button.type = 'زر'؛ Button.className = 'قراءة المزيد-btn'؛ var ButtonLabel = document.createElement('span'); ButtonLabel.className = 'قراءة المزيد-btn-label'؛ ButtonLabel.textContent = CONFIG.buttonText; Button.appendChild(buttonLabel); إذا (CONFIG.buttonSubText) { var ButtonSubText = document.createElement('span'); ButtonSubText.className = 'قراءة المزيد-btn-subtext'; ButtonSubText.textContent = CONFIG.buttonSubText; Button.appendChild(buttonSubText); } Button.addEventListener('click', function () {expand(container, overlay, fullHeight);schedulEmpLocalReorder(container);كشفAuthorAndTags(authorTagEls); }); overlay.appendChild(button); حاوية.appendChild(overlay); } // أداة الفيديو ".editor-tv-wrapper" ليست جزءًا من HTML المعروض على الخادم الأولي للمقال – يقوم برنامج نصي منفصل لعنصر واجهة المستخدم بإدخاله // من جانب العميل، وفقًا لجدوله الزمني الخاص. إذا تم تنفيذ تنبؤ setUpReadMore لمرة واحدة // قبل حدوث هذا الإدخال، فإنه يقيس الحاوية التي لا تحتوي على // فيديو حتى الآن ويحصل على الارتفاع الكامل الخطأ (صغير جدًا) – وهو // بالضبط ما يتم تشغيله في وقت مبكر جدًا (على سبيل المثال، التحميل البارد وغير المخزن مؤقتًا حيث لم يتم تنفيذ البرامج النصية الأخرى بعد) يتم إعادة إنتاجه، مقابل التحديث أحيانًا // الذي يحدث للفوز بالسباق. بدلاً من إعادة القياس بعد حدوثه // (رد الفعل)، انتظر ظهور الفيديو الأولي – تأخير لمرة واحدة // "المتطلبات المسبقة ليست جاهزة بعد"، وليس مراقبة مستمرة لتغيير الحجم – ثم // قم بتشغيل الإعداد التنبئي العادي لللقطة الواحدة. وظيفة waitForVideoThenSetUp(container) { if (container.querySelector('.editor-tv-wrapper')) { setUpReadMore(container); يعود؛ } var Setup = false; var Observer = new MutationObserver(function () { if (setted || !container.querySelector('.editor-tv-wrapper')) return; Setup = true; Observer.disconnect(); setUpReadMore(container); }); Observer.observe(container, { قائمة الطفل: صحيح، الشجرة الفرعية: صحيح }); // شبكة الأمان: إذا لم يظهر أي فيديو خلال بضع ثوانٍ، تعامل مع هذا على أنه // مقالة بدون فيديو بدلاً من الانتظار إلى الأبد. window.setTimeout(function () { if (استقر) العودة؛ استقر = صحيح؛ المراقب. قطع الاتصال ()؛ setUpReadMore (حاوية)؛ }، 5000)؛ } function init() { var Containers = document.querySelectorAll(CONFIG.selector); إذا (!containers.length) { console.warn('[ReadMore] لم يتم العثور على عناصر مطابقة للمحدد "' + CONFIG.selector + '". لم يتم تطبيق سلوك قراءة المزيد.'); يعود؛ } injectStyles(); Containers.forEach(waitForVideoThenSetUp); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); } else { init(); } })();