למפתחים/סקשנים מותאמים

סקשנים מותאמים

מדריך מלא ליצירת סקשנים מותאמים אישית שבעלי חנויות יכולים לערוך בבילדר - ללא כתיבת React.

סקירה כללית

סקשנים מותאמים (Developer Sections) מאפשרים למפתחים ליצור סקשנים ויזואליים עם HTML + CSS + Schema. בעל החנות יכול להוסיף אותם לכל עמוד דרך הבילדר ולערוך את ההגדרות שלהם (צבעים, טקסטים, תמונות וכו') בלי לגעת בקוד.

איך זה עובד:

  1. מפתח יוצר תבנית - HTML עם placeholders, CSS לעיצוב, ו-JSON Schema שמגדיר אילו שדות ניתנים לעריכה.
  2. הבילדר מייצר פאנל הגדרות - אוטומטית, מתוך ה-Schema. כל סוג שדה (text, color, image...) מקבל קונטרול מתאים.
  3. בעל חנות עורך את הסקשן - משנה טקסטים, צבעים ותמונות. כל שינוי מתעדכן בזמן אמת בתצוגה מקדימה.
  4. רינדור בצד שרת (SSR) - הסקשן מרונדר עם הערכים הסופיים. CSS מבודד אוטומטית.

איך מתחילים

  1. היכנסו להגדרות חנות ← סקשנים מותאמים באדמין.
  2. לחצו על סקשן חדש.
  3. הגדירו שם (למשל: "באנר פרומו") ו-Slug (למשל: promo-banner).
  4. כתבו את ה-HTML בטאב HTML, את ה-CSS בטאב CSS, ואת הגדרת השדות בטאב Schema.
  5. שמרו. הסקשן יופיע בבילדר תחת הקטגוריה "מותאם אישית".

תחביר התבנית

התבנית היא HTML רגיל עם placeholders. המנוע מחליף את ה-placeholders בערכים שבעל החנות הגדיר.

ערך מוחלף (Escaped)

השתמשו ב-{{settings.key}} כדי להציג ערך. הערך עובר HTML escaping אוטומטי (בטוח מ-XSS):

HTML
<h2>{{settings.title}}</h2>
<p>{{settings.subtitle}}</p>
<a href="{{settings.buttonLink}}">{{settings.buttonText}}</a>

ערך Raw (מסונן)

לתוכן עשיר (rich text) שכולל HTML בטוח, השתמשו בשלוש סוגריים {{{settings.key}}}. הערך עובר סינון (sanitization) שמוריד תגיות מסוכנות אבל משאיר עיצוב בסיסי:

HTML
<div class="content">
  {{{settings.richContent}}}
</div>
שימו לב: השתמשו ב-triple stache רק לשדות מסוג richtext. לטקסט רגיל העדיפו תמיד double stache.

הגדרת Schema

ה-Schema הוא JSON שמגדיר אילו שדות יופיעו בפאנל ההגדרות של הבילדר. כל שדה מוגדר עם key (מזהה), label (שם תצוגה), type (סוג), וערך default אופציונלי.

schema.json
{
  "fields": [
    {
      "key": "title",
      "label": "כותרת",
      "type": "text",
      "default": "ברוכים הבאים"
    },
    {
      "key": "backgroundColor",
      "label": "צבע רקע",
      "type": "color",
      "default": "#ffffff"
    },
    {
      "key": "showButton",
      "label": "הצג כפתור",
      "type": "boolean",
      "default": true
    },
    {
      "key": "imageUrl",
      "label": "תמונה ראשית",
      "type": "image"
    }
  ]
}

ה-key הוא מה שמשתמשים בו בתבנית: {{settings.title}}, {{settings.backgroundColor}}, וכו'.

סוגי שדות

textplaceholder

שדה טקסט שורה אחת

textareaplaceholder

שדה טקסט מרובה שורות

richtext

שדה טקסט עם HTML

numbermin, max, step

סליידר מספרי

rangemin, max, step

סליידר מספרי

color

בוחר צבע

image

שדה URL לתמונה

boolean

מתג (Toggle)

selectoptions

רשימה נפתחת

linkplaceholder

שדה קישור URL

דוגמת שדה select

schema.json
{
  "key": "textAlign",
  "label": "יישור טקסט",
  "type": "select",
  "default": "center",
  "options": [
    { "value": "right", "label": "ימין" },
    { "value": "center", "label": "מרכז" },
    { "value": "left", "label": "שמאל" }
  ]
}

דוגמת שדה range

schema.json
{
  "key": "columns",
  "label": "מספר עמודות",
  "type": "range",
  "min": 1,
  "max": 6,
  "step": 1,
  "default": 3
}

CSS ועיצוב

כתבו CSS רגיל בטאב CSS. המערכת מבודדת את ה-CSS אוטומטית לסקשן הספציפי באמצעות[data-section-id]attribute selector - כך שאין סכנה של התנגשויות עם שאר העמוד.

CSS דינמי

ניתן להשתמש ב-{{settings.key}} גם בתוך ערכי CSS:

CSS
.promo-banner {
  padding: 60px 20px;
  text-align: center;
  background-color: {{settings.backgroundColor}};
}

.promo-banner h2 {
  font-size: 36px;
  color: {{settings.titleColor}};
}

.promo-banner .btn {
  background: {{settings.buttonColor}};
  color: #fff;
  padding: 12px 32px;
  border-radius: 8px;
  text-decoration: none;
  display: inline-block;
}
CSS Scoping: כל הסלקטורים מקבלים אוטומטית prefix של [data-section-id="..."]. לדוגמה, .btn הופך ל-[data-section-id="abc"] .btn. אין צורך לעשות scoping ידני.

Media Queries

@media queries עובדים כרגיל. הסלקטורים בתוך ה-media query מקבלים scoping אוטומטי:

CSS
@media (max-width: 768px) {
  .promo-banner h2 {
    font-size: 24px;
  }
}

תנאים (Conditionals)

ניתן להציג או להסתיר חלקים מהתבנית לפי ערכי ההגדרות:

{{#if}} - הצג אם ערך קיים

HTML
{{#if settings.showButton}}
  <a href="{{settings.buttonLink}}" class="btn">
    {{settings.buttonText}}
  </a>
{{/if}}

הבלוק יוצג רק אם הערך "truthy" - כלומר לא false, לא 0, לא מחרוזת ריקה, ולא null.

{{#unless}} - הצג אם ערך לא קיים

HTML
{{#unless settings.imageUrl}}
  <div class="placeholder">אין תמונה</div>
{{/unless}}

תנאים מקוננים

ניתן לקנן תנאים:

HTML
{{#if settings.showHero}}
  <div class="hero">
    <h1>{{settings.title}}</h1>
    {{#if settings.showSubtitle}}
      <p>{{settings.subtitle}}</p>
    {{/if}}
  </div>
{{/if}}

אבטחה

  • HTML Escaping: כל ערך ב-{{settings.key}} עובר escaping אוטומטי. תווים כמו <, >, & מומרים לישויות HTML בטוחות.
  • Raw Sanitization: ערכים ב-{{{settings.key}}} עוברים סינון שמוריד תגיות <script>, event handlers, ו-javascript: URLs.
  • CSS Validation: ערכי CSS מאומתים - ביטויים כמו expression(), javascript:, ו-@import חסומים.
  • No eval: המנוע מבוסס על regex/string replacement בלבד. אין שימוש ב-eval() או new Function() בשום שלב.
  • CSS Scoping: כל CSS מבודד לסקשן הספציפי - אין אפשרות להשפיע על סקשנים אחרים או על הלייאוט הכללי.
JavaScript חיצוני:ניתן להטמיע widgets חיצוניים (כמו סקריפטים של צד שלישי) בתבנית HTML, בדומה לסקשן "HTML מותאם" הקיים. האחריות על הקוד החיצוני היא של המפתח.

דוגמאות מלאות

1. באנר פרומו

באנר פשוט עם כותרת, תת-כותרת, תמונת רקע וכפתור.

HTML
<div class="promo-banner">
  {{#if settings.imageUrl}}
    <img src="{{settings.imageUrl}}" alt="{{settings.title}}" />
  {{/if}}
  <div class="promo-content">
    <h2>{{settings.title}}</h2>
    {{#if settings.subtitle}}
      <p class="subtitle">{{settings.subtitle}}</p>
    {{/if}}
    {{#if settings.showButton}}
      <a href="{{settings.buttonLink}}" class="btn">
        {{settings.buttonText}}
      </a>
    {{/if}}
  </div>
</div>
CSS
.promo-banner {
  position: relative;
  padding: 80px 20px;
  text-align: center;
  background-color: {{settings.bgColor}};
  overflow: hidden;
}
.promo-banner img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0.3;
}
.promo-content {
  position: relative;
  z-index: 1;
  max-width: 600px;
  margin: 0 auto;
}
.promo-banner h2 {
  font-size: 42px;
  font-weight: 800;
  color: {{settings.titleColor}};
  margin-bottom: 12px;
}
.subtitle {
  font-size: 18px;
  color: {{settings.subtitleColor}};
  margin-bottom: 32px;
}
.btn {
  display: inline-block;
  padding: 14px 40px;
  background: {{settings.buttonColor}};
  color: #fff;
  text-decoration: none;
  border-radius: 50px;
  font-weight: 600;
  font-size: 16px;
  transition: transform 0.2s, opacity 0.2s;
}
.btn:hover {
  opacity: 0.9;
  transform: translateY(-1px);
}
@media (max-width: 768px) {
  .promo-banner { padding: 50px 16px; }
  .promo-banner h2 { font-size: 28px; }
}
schema.json
{
  "fields": [
    { "key": "title", "label": "כותרת", "type": "text", "default": "מבצע מיוחד!" },
    { "key": "subtitle", "label": "תת כותרת", "type": "text", "default": "הנחות של עד 50%" },
    { "key": "imageUrl", "label": "תמונת רקע", "type": "image" },
    { "key": "bgColor", "label": "צבע רקע", "type": "color", "default": "#1a1a2e" },
    { "key": "titleColor", "label": "צבע כותרת", "type": "color", "default": "#ffffff" },
    { "key": "subtitleColor", "label": "צבע תת כותרת", "type": "color", "default": "#cccccc" },
    { "key": "showButton", "label": "הצג כפתור", "type": "boolean", "default": true },
    { "key": "buttonText", "label": "טקסט כפתור", "type": "text", "default": "לצפייה" },
    { "key": "buttonLink", "label": "קישור כפתור", "type": "link", "default": "/products" },
    { "key": "buttonColor", "label": "צבע כפתור", "type": "color", "default": "#e94560" }
  ]
}

2. גריד יתרונות

שלושה כרטיסים עם אייקונים ותיאורים.

HTML
<div class="features-grid">
  <h2>{{settings.title}}</h2>
  <div class="grid">
    <div class="card">
      <div class="icon">{{settings.icon1}}</div>
      <h3>{{settings.card1Title}}</h3>
      <p>{{settings.card1Desc}}</p>
    </div>
    <div class="card">
      <div class="icon">{{settings.icon2}}</div>
      <h3>{{settings.card2Title}}</h3>
      <p>{{settings.card2Desc}}</p>
    </div>
    <div class="card">
      <div class="icon">{{settings.icon3}}</div>
      <h3>{{settings.card3Title}}</h3>
      <p>{{settings.card3Desc}}</p>
    </div>
  </div>
</div>
CSS
.features-grid {
  padding: 60px 20px;
  text-align: center;
}
.features-grid h2 {
  font-size: 32px;
  font-weight: 700;
  margin-bottom: 40px;
  color: {{settings.titleColor}};
}
.grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 24px;
  max-width: 900px;
  margin: 0 auto;
}
.card {
  padding: 32px 24px;
  border-radius: 16px;
  background: {{settings.cardBg}};
  border: 1px solid #eee;
}
.icon { font-size: 32px; margin-bottom: 16px; }
.card h3 { font-size: 18px; font-weight: 700; margin-bottom: 8px; }
.card p { font-size: 14px; color: #666; line-height: 1.6; }
@media (max-width: 768px) {
  .grid { grid-template-columns: 1fr; }
}
schema.json
{
  "fields": [
    { "key": "title", "label": "כותרת ראשית", "type": "text", "default": "למה לבחור בנו?" },
    { "key": "titleColor", "label": "צבע כותרת", "type": "color", "default": "#111" },
    { "key": "cardBg", "label": "צבע רקע כרטיס", "type": "color", "default": "#fafafa" },
    { "key": "icon1", "label": "אייקון 1 (Emoji)", "type": "text", "default": "🚚" },
    { "key": "card1Title", "label": "כותרת כרטיס 1", "type": "text", "default": "משלוח חינם" },
    { "key": "card1Desc", "label": "תיאור כרטיס 1", "type": "textarea", "default": "משלוח חינם בהזמנה מעל 200 ₪" },
    { "key": "icon2", "label": "אייקון 2 (Emoji)", "type": "text", "default": "🔄" },
    { "key": "card2Title", "label": "כותרת כרטיס 2", "type": "text", "default": "החזרה קלה" },
    { "key": "card2Desc", "label": "תיאור כרטיס 2", "type": "textarea", "default": "החזרה חינם תוך 30 יום" },
    { "key": "icon3", "label": "אייקון 3 (Emoji)", "type": "text", "default": "💬" },
    { "key": "card3Title", "label": "כותרת כרטיס 3", "type": "text", "default": "תמיכה 24/7" },
    { "key": "card3Desc", "label": "תיאור כרטיס 3", "type": "textarea", "default": "צוות שלנו זמין תמיד לעזור" }
  ]
}

3. באנר ספירה לאחור (עם JS חיצוני)

דוגמה להטמעת JavaScript חיצוני בסקשן:

HTML + JS
<div class="countdown-banner">
  <h2>{{settings.title}}</h2>
  <div id="countdown-timer" class="timer"></div>
  <p>{{settings.subtitle}}</p>
</div>

<script>
  (function() {
    const end = new Date('{{settings.endDate}}').getTime();
    const el = document.getElementById('countdown-timer');
    if (!el) return;
    const tick = () => {
      const now = Date.now();
      const diff = Math.max(0, end - now);
      const d = Math.floor(diff / 86400000);
      const h = Math.floor((diff % 86400000) / 3600000);
      const m = Math.floor((diff % 3600000) / 60000);
      const s = Math.floor((diff % 60000) / 1000);
      el.textContent = d+'d '+h+'h '+m+'m '+s+'s';
      if (diff > 0) requestAnimationFrame(tick);
    };
    tick();
  })();
</script>
שימו לב:JavaScript רץ בצד הלקוח בלבד, בדומה לסקשן "HTML מותאם" הקיים. המפתח אחראי על הקוד. מומלץ להשתמש ב-IIFE כדי למנוע זיהום של ה-global scope.

טיפים ו-Best Practices

  • שמות ברורים: בחרו key שמתאר את המטרה (heroTitle ולא text1).
  • ברירות מחדל: תמיד הגדירו default - זה מה שבעל החנות רואה כשהוא מוסיף את הסקשן.
  • רספונסיביות: כתבו media queries בטאב CSS. הסקשנים רצים בתוך כל סוגי מסך.
  • שדות מותנים: השתמשו ב-{{#if}} סביב אלמנטים אופציונליים (כפתור, תמונה, וכו').
  • CSS Scoping: אין צורך ב-BEM או prefix מיוחד - המערכת מבודדת את ה-CSS בשבילכם.
  • תצוגה מקדימה: עורך האדמין מציג תצוגה מקדימה חיה עם ברירות המחדל. בדקו שהסקשן נראה טוב לפני שמירה.
  • פשטות: עדיף כמה סקשנים פשוטים מאשר סקשן אחד מסובך עם 20 שדות.
  • צבעים: השתמשו בשדות color עבור צבעים דינמיים. בוחר הצבע בבילדר חזק ונוח.

מגבלות נוכחיות

  • ללא לולאות: אין תמיכה ב-{{#each}} כרגע. לפריטים חוזרים, צרו שדות מספריים (item1Title, item2Title...) או סקשנים נפרדים.
  • ללא גישה ל-DB: הסקשנים הם front-end בלבד. אין אפשרות לשלוף מוצרים, הזמנות וכו' מתוך התבנית.
  • ללא React: התבניות הן HTML+CSS סטטי (עם placeholders). לקומפוננטות React אינטראקטיביות, פנו לפיתוח פלאגין.