على منتدى الخاص بي، أردت أن أمنح المستخدمين بعض خيارات التخصيص للمظهر الافتراضي. أردت أن أمنحهم خيار تعيين خلفية للمنتدى. يمكنني إنشاء سمات مختلفة لهذا الغرض، ولكن هذا يتطلب الكثير من العمل الإضافي. وفي حين أن السمات لها إعدادات، إلا أنها للجميع وليست خاصة بمستخدم معين. ولكن هناك طريقة لتحقيق إعدادات قابلة للتكوين من قبل المستخدم للسمات، وذلك عن طريق “إساءة استخدام” حقول المستخدم.
في السمة الخاصة بي، لدى المستخدم 3 إعدادات للخلفية (الصورة، المزج مع لون الخلفية، والشفافية الشبيهة بالمحتوى)، ولكن في هذا المنشور سأركز فقط على إعداد واحد، صورة الخلفية.
الخطوة 1: إنشاء حقل المستخدم
لدى المستخدم خيار تحديد مجموعة من الخلفيات المختلفة، أو عدم وجود خلفية على الإطلاق. بالنسبة لحقل المستخدم، قمت بإنشاء حقل قائمة منسدلة اختياري بالخيارات التالية:
- لا شيء – لا توجد خلفية على الإطلاق، هذا مهم
- افتراضي – إذا لم يقم المستخدم بإجراء تحديد
- بديل واحد
- بديل اثنان
هذا حقل اختياري، لذا ستكون القيمة الافتراضية فارغة. بالنسبة للقيمة الفارغة، أود استخدام الخلفية الافتراضية.
الخطوة 2: CSS
في common/common.scss قمت بتعريف ما يلي:
:root {
--wallpaper-default: url(#{$img-wallpaper-default});
--wallpaper-alternative-one: url(#{$img-wallpaper-alternative-one});
--wallpaper-alternative-two: url(#{$img-wallpaper-alternative-two});
/* الافتراضي إلى لا شيء لتقليل التبديل المحتمل للصورة الأولية */
--wallpaper: none;
}
body {
background-image: var(--wallpaper);
}
يحتوي :root على متغيرات لصور الخلفيات المختلفة، وهي أصول السمة. تم تعيين صورة الخلفية للمتغير --wallpaper، وهو في الأصل none. لذلك افتراضيًا عند تحميل CSS لن تكون هناك خلفية. يتم ذلك لإزالة “وميض” محتمل عندما يختار المستخدم خلفية تختلف عن الأصلية.
تحصل كل خلفية قابلة للتحديد على متغير --wallpaper-xx-yy-zz. قيم حقول المستخدم هي نص عادي. بناءً على هذه القيمة، سأختار متغير CSS الصحيح لاستخدامه كصورة خلفية.
الخطوة 3: إعدادات السمة
في كود JavaScript، أحتاج إلى معرفة حقل المستخدم الذي يحتوي على إعداد الخلفية. أسهل طريقة للحصول على هذا هي الإشارة مباشرة إلى معرف حقل المستخدم. لذا تحتاج إلى إنشاء إعداد سمة لتكوين معرف حقل المستخدم هذا
wallpaper_user_field:
default: -1
type: integer
الخطوة 4: تعيين الخلفية
لهذا تحتاج إلى كتابة بعض JavaScript. نظرًا لأن هذا الكود يحتاج إلى التنفيذ مبكرًا وفي أقرب وقت ممكن، ستقوم بإنشاء مُهيئ javascripts/discourse/initializer/your-theme-component.js.
لنبدأ بالأساسيات:
import { setOwner } from '@ember/owner';
import { withPluginApi } from 'discourse/lib/plugin-api';
const PLUGIN_ID = 'your-theme-component';
class YourThemeWallpaper {
api;
styleRoot;
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// جلب عنصر `html`
this.styleRoot = document.body.parentElement.style;
this.loadWallpaperSettings();
}
// سيتم إضافة المزيد من المنطق لاحقًا
}
export default {
name: PLUGIN_ID,
initialize(owner) {
withPluginApi('1.34.0', (api) => {
this.instance = new YourThemeWallpaper(owner, api);
});
}
}
هذا هو الكود الأساسي لإضافة JavaScript لمكون السمة. يجب أن يشير المتغير styleRoot إلى عنصر html في المستند، والذي سنستخدمه لتغيير CSS الفعال.
ستقوم الدالة loadWallpaperSettings بتحميل إعداد المستخدم فعليًا، وتبدو مشابهة لهذا:
loadWallpaperSettings() {
let user = this.api.getCurrentUser();
if (user) {
this.api.container.lookup('store:main').find('user', user.username).then((user) => {
let wp = user.user_fields[settings.wallpaper_user_field] || '';
this.setWallpaper(wp);
});
} else {
this.setWallpaper('');
}
}
للحصول على قيم حقول المستخدم، يجب إجراء مكالمة API إضافية، والتي قد تستغرق بعض الوقت. بمجرد عودة هذه المكالمة، نحصل على قيمة حقل المستخدم ونستدعي setWallpaper بهذه القيمة. settings.wallpaper_user_field هو مرجع لإعداد السمة من الخطوة #3. إذا لم يتم تكوين الإعداد، فستكون قيمة wp فارغة افتراضيًا.
إذا لم يكن هناك مستخدم حالي، نستدعي الدالة setWallpaper بسلسلة فارغة لتحميل الخلفية الافتراضية.
الدالة setWallpaper ستقوم بتحديث الخلفية فعليًا:
setWallpaper(wallpaper) {
if (!wallpaper || wallpaper === '') {
// استخدام الافتراضي إذا لم يتم تعيين أي شيء
wallpaper = 'default';
}
// تطبيع القيمة لتناسب متغير CSS
wallpaper = wallpaper.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-');
if (wallpaper === 'none') {
this.styleRoot.setProperty('--wallpaper', 'none');
} else {
this.styleRoot.setProperty('--wallpaper', 'var(--wallpaper-'+wallpaper+')');
}
}
إذا كانت إعدادات الخلفية المكونة هي "none"، فإننا نزيل صورة الخلفية. بخلاف ذلك، يتم تطبيع القيمة لتبدو كمتغير CSS. لذا سيؤدي “Alternative One” إلى تعيين متغير CSS --wallpaper إلى var(--wallpaper-alternative-one). إذا لم تتطابق قيمة حقل المستخدم مع متغير CSS، فسينتج عن ذلك فعليًا عدم وجود خلفية، وهذا جيد بما فيه الكفاية.
الخطوة 5: تحميل أسرع
الإعداد أعلاه يعمل بشكل رائع. ومع ذلك، يمكن أن تؤدي مكالمة API الإضافية إلى تعيين الخلفية في وقت متأخر جدًا. وهذا لا ينتج عنه نتيجة رائعة. لحل هذه المشكلة، أستفيد من التخزين المحلي للمتصفح لتخزين واسترداد الإعداد.
في المُنشئ، قبل تحميل إعدادات خلفية المستخدم، حاول أولاً تعيين الخلفية بناءً على البيانات من التخزين المحلي. عند تحميل إعدادات المستخدم، قم بتحديث التخزين المحلي بما تم تكوينه.
const WALLPAPER_KEY = 'your_theme_wallpaper';
constructor(owner, api) {
setOwner(this, owner);
this.api = api;
// جلب عنصر `html`
this.styleRoot = document.body.parentElement.style;
this.loadLocalStorage();
this.loadWallpaperSettings();
}
loadWallpaperSettings() {
let user = this.api.getCurrentUser();
if (user) {
this.api.container.lookup('store:main').find('user', user.username).then((user) => {
let wp = user.user_fields[settings.wallpaper_user_field] || '';
localStorage.setItem(WALLPAPER_KEY, wp);
this.setWallpaper(wp);
});
} else {
localStorage.removeItem(WALLPAPER_KEY);
this.setWallpaper('');
}
}
loadLocalStorage() {
let data = localStorage.getItem(WALLPAPER_KEY);
if (data) {
this.setWallpaper(data);
}
}
هذا يحسن بشكل كبير تعيين الخلفية في وقت مبكر من تحميل Discourse. فعليًا سيتم استدعاء الدالة setWallpaper مرتين. ولكن نظرًا لأن القيمة نادرًا ما تتغير، فلن يكون هذا ملحوظًا. فقط إذا قام المستخدم بتغيير الخلفية، فستومض أثناء التحميل التالي. هذا هو أيضًا السبب في أن الخلفية الأولية في CSS تم تعيينها على none بدلاً من الافتراضي.
الخطوة 6: معاينة حية
عند تغيير نظام الألوان في Discourse، تحصل على معاينة حية لنظام الألوان الجديد هذا، ولا يتعين عليك الحفظ وإعادة التحميل لمعرفة كيف سيبدو. سيكون هذا لطيفًا جدًا أيضًا لإعدادات الخلفية حتى يتمكن المستخدمون من التحقق من أي منها يفضلونه.
لتحقيق ذلك، تحتاج إلى بعض الحيل الإضافية عن طريق تعديل مكونات حقول المستخدم.
في نهاية المُنشئ، أضفت استدعاءً لـ this.setupLivePreview() الذي يحتوي على ما يلي:
setupLivePreview() {
api.onPageChange((url, title) => {
if (this.wallpaperPreviewed) {
this.wallpaperPreviewed = false;
this.loadWallpaperSettings();
}
});
let _this = this;
api.modifyClass('component:user-fields/dropdown', {
pluginId: PLUGIN_ID,
didUpdateAttrs() {
if (this.field.id == settings.wallpaper_user_field) {
_this.wallpaperPreviewed = true;
_this.setWallpaper(this.value);
}
}
});
}
في الجزء الأول، نستمع إلى أحداث تغيير الصفحة. إذا كانت wallpaperPreviewed صحيحة، نقوم بإعادة تحميل الإعدادات الأصلية للمستخدم. إذا لم يقم المستخدم بحفظ القيم الجديدة لحقول المستخدم، فسيتم استعادة الخلفية إلى الإعداد الأصلي.
التالي هو السحر الحقيقي. نقوم بتعديل مكون حقل المستخدم المنسدل لإضافة بعض التعليمات البرمجية الإضافية إلى الدالة didUpdateAttrs. سيتم استدعاء هذا إذا حصل حقل المستخدم على قيمة جديدة. عند حدوث ذلك، نقوم بتعيين المتغير wallpaperPreviewed إلى صحيح، بحيث عند تغيير الصفحة سيتم تحميل الإعدادات المحفوظة. بعد ذلك، نستدعي الدالة setWallpaper لعرض الخلفية المحددة حاليًا بدلاً من ذلك.
يمكن القيام بنفس الشيء لأنواع حقول المستخدم الأخرى. على سبيل المثال، بالنسبة لحقل النص العادي، ستقوم بتعديل الفئة component:user-fields/text.
الخلاصة
بهذا، تمنح المستخدمين بعض التحكم في كيفية تأثير مكونات السمة على تجربتهم من خلال حقول المستخدم.
المشكلة الوحيدة هي أن حقول المستخدم موجودة تحت التفضيلات > الملف الشخصي بدلاً من التفضيلات > الواجهة حيث قد ترغب في وضعها.