1/** 2 * æ§é åãã¼ã¿ JSON-LD: Product + offers(Offer) 3 * è¦ä»¶: ã«ã¼ãã Product ã¨ãããã®ä¸ã® offers ã« Offer ãªãã¸ã§ã¯ããæ ¼ç´ã 4 * ãã¼ã¸è¦ç´ (title / meta description / canonical) ã¨å¼æ°ããåççæã 5 * <script type="application/ld+json" id="offer-json-ld"> ãæ¿å ¥ãã¾ãã 6 * 7 * çæä¾ (åçé¨å㯠[] 表è¨): 8 * { 9 * "@context": "https://schema.org/", 10 * "@type": "Product", 11 * "name": "LINEMO", 12 * "description": "LINEMOã¯ã½ãããã³ã¯ã®ãªã³ã©ã¤ã³å°ç¨æºå¸¯é»è©±ãµã¼ãã¹ã§ãã", 13 * "url": "https://www.linemo.jp/", 14 * "category": "æ ¼å®SIM/æ ¼å®ã¹ãã", 15 * "brand": { "@type": "Brand", "name": "LINEMO" }, 16 * "image": "https://www.linemo.jp/assets/images/common/logo.svg", 17 * "offers": { 18 * "@type": "Offer", 19 * "name": "[titleã¿ã°ã®ããã¹ãä¸ãæåã®ï½ä»¥éãé¤ããé¨å]", 20 * "description": "[meta descriptionã¿ã°ã®content]", 21 * "availabilityStarts": "[YYYY-MM-DD]", 22 * "availabilityEnds": "[YYYY-MM-DD(ä»»æ)]", 23 * "eligibleRegion": { "@type": "Country", "name": "JP" }, 24 * "priceSpecification": { 25 * "@type": "PriceSpecification", 26 * "price": "[æ°å¤]", 27 * "priceCurrency": "JPY", 28 * "valueAddedTaxIncluded": [true/false] 29 * }, 30 * "additionalProperty": { 31 * "@type": "PropertyValue", 32 * "name": "campaignURL", 33 * "value": "[canonical URL]" 34 * } 35 * } 36 * } 37 * 38 * ä½¿ç¨æ¹æ³(ä¾): åãã¼ã¸ã§ãã®JSãèªã¿è¾¼ã¿ããã以éã®è¡ã«ä»¥ä¸ãã¤ã³ã©ã¤ã³è¨è¿° 39 * document.addEventListener('DOMContentLoaded', function(){ 40 * window.insertOfferJsonLd({ 41 * availabilityStarts: '2025-10-01', 42 * availabilityEnds: '2025-10-31', // ä»»æ 43 * price: 0, // æ°å¤ or æ°åæåå 44 * valueAddedTaxIncluded: false // boolean 45 * }); 46 * }); 47 * ãã£ã³ãã¼ã³ãè¤æ°ããå ´å 48 * document.addEventListener('DOMContentLoaded', function(){ 49 * window.insertOfferJsonLd({ 50 * offerList: [ 51 * { 52 * availabilityStarts: '2025-10-01', 53 * availabilityEnds: '2025-10-31', 54 * price: 0, 55 * valueAddedTaxIncluded: false 56 * }, 57 * { 58 * availabilityStarts: '2025-12-01', 59 * availabilityEnds: '2025-12-31', 60 * price: 1000, 61 * valueAddedTaxIncluded: true, 62 * name: 'ã¯ãªã¹ãã¹ãã£ã³ãã¼ã³', 63 * description: '12æéå®ã®å²å¼æ½çã§ãã', 64 * campaignURL: 'https://www.linemo.jp/campaign/xmas2025/' 65 * } 66 * ] 67 * æ¢åã® script#offer-json-ld ãããã°ç½®ãæãã¾ãã 68 * äºææ§: 颿°å insertOfferJsonLd ãç¶ç¶å©ç¨ (æ§ API ã¨ã®å·®ç°ã¯æ»ã夿§é )ã 69 */ 70 71(function(global){ 72 'use strict'; 73 74 var SCRIPT_ID = 'offer-json-ld'; 75 var DATE_RE = /^\d{4}-\d{2}-\d{2}$/; // YYYY-MM-DD 76 77 /** 78 * æ¥ä»æååããªãã¼ã·ã§ã³ (YYYY-MM-DD) 79 * @param {string|undefined|null} s 80 * @returns {boolean} 81 */ 82 function isValidDateStr(s){ 83 if(!s || typeof s !== 'string') return false; 84 if(!DATE_RE.test(s)) return false; 85 // ç°¡æå®å¨æ¥ãã§ãã¯: Dateã§ãã¼ã¹ãã¦é夿ä¸è´ 86 var d = new Date(s + 'T00:00:00Z'); 87 if(isNaN(d.getTime())) return false; 88 var iso = d.toISOString().slice(0,10); 89 return iso === s; 90 } 91 92 /** 93 * titleã¿ã°ã®æåã® 'ï½'(å ¨è§) 以åãåå¾ããªããã°å ¨æã 94 * @returns {string|undefined} 95 */ 96 function extractTitlePart(){ 97 var el = document.querySelector('title'); 98 if(!el) return undefined; 99 var text = el.textContent || ''; 100 var idx = text.indexOf('ï½'); 101 if(idx === -1) return text.trim(); 102 return text.slice(0, idx).trim(); 103 } 104 105 /** 106 * meta description ãåå¾ã 107 * @returns {string|undefined} 108 */ 109 function getMetaDescription(){ 110 var meta = document.querySelector('meta[name="description"]'); 111 if(meta && meta.getAttribute('content')) return meta.getAttribute('content').trim(); 112 // ãã©ã¼ã«ããã¯: og:description 113 var og = document.querySelector('meta[property="og:description"]'); 114 if(og && og.getAttribute('content')) return og.getAttribute('content').trim(); 115 return undefined; 116 } 117 118 /** 119 * canonical URL ãåå¾ã 120 * @returns {string|undefined} 121 */ 122 function getCanonical(){ 123 var link = document.querySelector('link[rel="canonical"]'); 124 if(link && link.getAttribute('href')) return link.getAttribute('href').trim(); 125 // ãã©ã¼ã«ããã¯: location.href 126 return (typeof location !== 'undefined') ? location.href : undefined; 127 } 128 129 /** 130 * Product + Offer JSON-LD ãªãã¸ã§ã¯ããçµã¿ç«ã¦ (è¤æ°Offer対å¿) 131 * @param {Object} opt 132 * @param {string} [opt.availabilityStarts] (å䏿å®ç¨ å¿ é YYYY-MM-DD) â» offerListæªä½¿ç¨æ 133 * @param {string} [opt.availabilityEnds] (å䏿å®ç¨ ä»»æ YYYY-MM-DD) 134 * @param {number|string} [opt.price] (å䏿å®ç¨ ä»»æ æ°å¤ã¾ãã¯æ°åæååãæªæå®ãªã 0) 135 * @param {boolean|string} [opt.valueAddedTaxIncluded] (å䏿å®ç¨ ä»»æ true/false ã¾ã㯠'true'/'false'. æªæå®ãªã false) 136 * @param {Array<Object>}
136 [opt.offerList] è¤æ°æå®ç¨ 137 * è¦ç´ : { availabilityStarts: string(å¿ é ), availabilityEnds?: string, price?: number|string, valueAddedTaxIncluded?: boolean|string, campaignURL?: string } 138 * @returns {Object|null} Product ã«ã¼ããªãã¸ã§ã¯ã 139 */ 140 function buildProductOffer(opt){ 141 opt = opt || {}; 142 var canonicalGlobal = getCanonical(); 143 144 function normalizePrice(p){ 145 var raw = (p === 0 || p) ? p : 0; 146 var s = '' + raw; 147 if(!/^[0-9]+(?:\.[0-9]+)?$/.test(s)){ 148 console.warn('[json-ld] price ãæ°å¤ã¨ãã¦è§£éã§ãã¾ããã0 ã使ç¨ãã¾ã'); 149 return '0'; 150 } 151 return s; 152 } 153 154 function normalizeVAT(v){ 155 if(typeof v === 'boolean') return v; 156 if(typeof v === 'string'){ 157 if(v.toLowerCase() === 'true') return true; 158 if(v.toLowerCase() === 'false') return false; 159 console.warn('[json-ld] valueAddedTaxIncluded æååã䏿£ã§ã (true/false)'); 160 } 161 return false; 162 } 163 164 function buildSingleOffer(src){ 165 if(!src || !isValidDateStr(src.availabilityStarts)){ 166 console.warn('[json-ld] availabilityStarts ã䏿£ã§ããYYYY-MM-DD å½¢å¼ã§æå®ãã¦ãã ãã (offeré¤å¤)'); 167 return null; 168 } 169 var endsValid = isValidDateStr(src.availabilityEnds); 170 return { 171 '@type': 'Offer', 172 // 2ä»¶ç®ä»¥éã¯ãã¼ã¸ããåå¾ã§ããªãã¨æ³å®ããããã overrideå¯ 173 name: (src.name && typeof src.name === 'string') ? src.name : extractTitlePart(), 174 description: (src.description && typeof src.description === 'string') ? src.description : getMetaDescription(), 175 availabilityStarts: src.availabilityStarts, 176 availabilityEnds: endsValid ? src.availabilityEnds : undefined, 177 eligibleRegion: { '@type': 'Country', name: 'JP' }, 178 priceSpecification: { 179 '@type': 'PriceSpecification', 180 price: normalizePrice(src.price), 181 priceCurrency: 'JPY', 182 valueAddedTaxIncluded: normalizeVAT(src.valueAddedTaxIncluded) 183 }, 184 additionalProperty: { 185 '@type': 'PropertyValue', 186 name: 'campaignURL', 187 value: (src.campaignURL && typeof src.campaignURL === 'string') ? src.campaignURL : canonicalGlobal 188 } 189 }; 190 } 191 192 var offersResult; 193 if(Array.isArray(opt.offerList)){ 194 offersResult = opt.offerList.map(buildSingleOffer).filter(Boolean); 195 if(offersResult.length === 0){ 196 console.warn('[json-ld] æå¹ãª Offer ãããã¾ãã (offerList)'); 197 return null; 198 } 199 // 1ä»¶ãªãObjectãè¤æ°ãªãé å 200 offersResult = offersResult.length === 1 ? offersResult[0] : offersResult; 201 } else { 202 // 徿¥ã®åä¸æå® 203 var single = buildSingleOffer({ 204 availabilityStarts: opt.availabilityStarts, 205 availabilityEnds: opt.availabilityEnds, 206 price: opt.price, 207 valueAddedTaxIncluded: opt.valueAddedTaxIncluded, 208 campaignURL: opt.campaignURL, 209 name: opt.name, 210 description: opt.description 211 }); 212 if(!single) return null; 213 offersResult = single; 214 } 215 216 var product = { 217 '@context': 'https://schema.org/', 218 '@type': 'Product', 219 name: 'LINEMO', 220 description: 'LINEMOã¯ã½ãããã³ã¯ã®ãªã³ã©ã¤ã³å°ç¨æºå¸¯é»è©±ãµã¼ãã¹ã§ãã', 221 url: 'https://www.linemo.jp/', 222 category: 'æ ¼å®SIM/æ ¼å®ã¹ãã', 223 brand: { '@type': 'Brand', name: 'LINEMO' }, 224 image: 'https://www.linemo.jp/assets/images/common/logo.svg', 225 offers: offersResult 226 }; 227 return product; 228 } 229 230 /** 231 * scriptæ¿å ¥ (æ¢åã®åID scriptãç½®æ) 232 * @param {Object} offerObj JSONãªãã¸ã§ã¯ã 233 */ 234 function injectScript(offerObj){ 235 if(!offerObj) return; 236 try { 237 var json = JSON.stringify(offerObj, null, 2); 238 var head = document.head || document.getElementsByTagName('head')[0]; 239 if(!head){ 240 console.warn('[json-ld] headè¦ç´ ãè¦ã¤ããã¾ãã'); 241 return; 242 } 243 var prev = document.getElementById(SCRIPT_ID); 244 if(prev && prev.parentNode){ 245 prev.parentNode.removeChild(prev); 246 } 247 var s = document.createElement('script'); 248 s.type = 'application/ld+json'; 249 s.id = SCRIPT_ID; 250 s.text = json; 251 head.appendChild(s); 252 } catch(e){ 253 console.error('[json-ld] æ¿å ¥å¤±æ', e); 254 } 255 } 256 257 /** 258 * å ¬éAPI 259 * @param {Object} options 260 * @param {string} options.availabilityStarts YYYY-MM-DD (å¿ é ) 261 * @param {string} [options.availabilityEnds] YYYY-MM-DD (ä»»æ) 262 */ 263 function insertOfferJsonLd(options){ 264 var productObj = buildProductOffer(options || {}); 265 injectScript(productObj); 266 return productObj; // ãããã°ç¨ã«è¿ã 267 } 268 269 // ã°ãã¼ãã«å ¬é (ååè¡çªãé¿ãæ¢åã䏿¸ãããªã) 270 if(!global.insertOfferJsonLd){ 271 global.insertOfferJsonLd = insertOfferJsonLd; 272 } 273 274})(window || this);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.