{
  "-net-usertreffen-vom-montag.html": {
    "href": "-net-usertreffen-vom-montag.html",
    "title": ".NET Usertreffen vom Montag - Get Blogged by JoKi",
    "summary": ".NET Usertreffen vom Montag Copy Markdown View Markdown GitHub Markdown Source .NET Usertreffen vom Montag Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '-net-usertreffen-vom-montag'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '-net-usertreffen-vom-montag'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '.NET Usertreffen vom Montag') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-03 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Well, erst heute, aber dennoch möchte ich noch ein zwei Worte darüber verlieren... Der Abend war, sagen wir mal, 'interessant'... Erste Neuerung, an der Eingangstür vom Blechhammer hing eine Art Wegweiser aus. Wir scheinen also nicht die einzigen zu sein, die das Hotel für Treffen nutzt. Erfreulicherweise waren bereits Teilnehmer anwesend, obwohl wir ebenfalls zu früh waren. IIRC liefen wir um 19:20 ein, das Treffen geht so ca. 19:30 erst los. Weiterhin war's gleich mal wieder spannend zu sehen, wieviele ihre Laptops mit Problemen dabei hatten - Und so sind wir direkt erst einmal ins Problemfixing eingestiegen... Hier Probleme mit SQL Server 2005 Beta, dort Wehwehchen mit Visual Studio 2005 Beta, und dann wieder andere thematische Problemchen. Wow, ich bin begeistert... Einerseits, dass die Laptops mitgebracht werden, und andererseits, das in kleinen Grüppchen an Lösungen gewerkelt wird - klasse! Man könnte dies beinahe 'Offline-Forum' nennen... Leider, wie ich fand, lief der weitere Abend ein wenig flau weiter. Okay, die Themen - der Singleton und den Data Access Layer - sind sicherlich interessant, aber irgendwie hatte ich einen lauwarmen Eindruck - dunno, vielleicht hatte ich einfach nur einen schlechten Tag/Abend erwischt. Dennoch wurde viel diskutiert, wieso und waurm diverse Sachen auf die eine bzw. nicht auf die andere Art und Weise gelöst wurden. Insgesamt informativ, gerade für Einsteiger in die Materie um ADO.NET. Ich hab' zumindest wieder einiges mitnehmen können, auch wenn das eine oder andere nicht meinen Vorstellungen entspricht. Da beispielsweise die Begründung einiger Vorgehensweisen auf die Unzulänglichkeiten von anderen Programmiersprachen (nein, nicht VFP ) zurückzuführen ist. Und muss ich schlechte Konzepte wirklich weiter übernehmen, wenn es anders besser geht? Nee, oder? Der Ausblick auf das nächste Treffen am 6.6.2005 klang auf alle Fälle schon mal anregend. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = '-net-usertreffen-vom-montag.html'; this.page.identifier = '9271ed1c97_id-net-usertreffen-vom-montag'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "100-days-of-exam.html": {
    "href": "100-days-of-exam.html",
    "title": "100 Days of Exam - Get Blogged by JoKi",
    "summary": "100 Days of Exam Copy Markdown View Markdown GitHub Markdown Source 100 Days of Exam Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '100-days-of-exam'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '100-days-of-exam'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '100 Days of Exam') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-03-13 - Filed under ProjectsPersonalCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); A new adventure is on its way: #100DaysOfExam which has been inspired by two factors. First, my personal 30-day challenges I did in the past and the concept of 100 Days of Code by Alexander Kallaway. Much to my dislike I noticed during the last couple of months (or even years) that there are fewer challenges than in previous times. For example Microsoft's \"Know It, Prove It\" (KiPi) campaign isn't alive anymore. The KiPi challenge was about watching a curated list of online videos in order to learn (more) about a certain development stack or Microsoft technology. I really enjoyed that challenge several times, and managed to complete them within the given period of 30 days. Available resources Some years back I purchased an annual subscription with Pluralsight given their outstanding course material. Thanks to my active Microsoft MVP award this has been extended generously. Despite easy access to their library of 5,000+ courses I noticed that my visits were irregular and uncoordinated. Most of the time I searched through the video courses because I was looking for a specific technology I was working on. The Microsoft Virtual Academy (MVA) as well as Microsoft's Channel 9 websites offer a great range of learning material on various topics. Again, I only took advantage of this treasure trove when I needed certain information. My readers surely have different backgrounds and interest in technologies, and there are so many other great learning platforms available online. Recently, I started to be more active on edX. Mainly to educate myself on matters of data science, business intelligence and machine learning. It's great that edX runs a dual approach - accessing the course modules for free and offering the possibility to opt-in for a paid certificate, if needed. What are the (e)Learning platforms that you are using? It would be great to get a broader overview of what is available and at the same time your recommendations and comments on your suggested resources. Please use the comment section at the bottom. Much appreciated. Add a purpose I am not sure about your motivation of watching an online video course, for me it is always about to get a better understanding of a selected topic or technology. Most of the time this is induced by the circumstances that I am currently working on some project or have an upcoming requirement. Briefly, it is usually because of professional reasons. So far, this kind of browsing material has been kind of chaotic. Either it happened on short notice or it had a short lifetime expectancy. Nonetheless I gained more knowledge about a technology and I was able to get the job done. Though I have to admit that it didn't feel right this way. It was more like a cliffhanger. Leaving an empty space of information that could have been filled on the one hand, and missing the opportunity to test the acquired knowledge on the other hand. That's exactly where #100DaysOfExam comes into play. Here's the core statement of the challenge: \"I will learn and prepare for an exam for at least an hour every day for the next 100 days.\" Define your goal and commit yourself to the challenge. Do some research on the exam topics, collect and bookmark new resources, get your hands wet with some cool tips and tricks. And do it daily. \"Feel\" the challenge From the first day on you are going to tweet about your progress daily. Using the hashtag #100DaysOfExam you are going to report about your activities during the day to others. This way, over time, there will be more participation in this challenge. Perhaps you might find someone else with same or similar goals, and you both team up in a regular learning group. Even then use Twitter connection between you and other participations to pay it forward. Encourage others, give them feedback where needed, share your resources and knowledge for the greater good. But remember: Be polite and excellent to each other. Recently, I signed up for the #BlogMore section of the #CNC2018 challenge in order to commit myself to write more regularly on this and other blogs. Yes, it was tough at the beginning. Not to ponder about possible topics but simply to find the motivation and actually start typing the content. The #BlogMore challenge got me in touch with other bloggers via Twitter and a designated group on Facebook. It felt great to read about the musings of others, and to receive the comments of other readers regarding your ideas and experience. After all, it felt good and right to write more... How to get started? Well, I can't tell you... You got an exam coming soon? You want to dive into a new field of information? You always wanted to dig deeper into a certain topic? You need motivation through others to stick to the plan? If there is at least one \"Yes\" among the answers I would say you are already one third on your way into this challenge. Now, move over to the #100DaysOfExam repository on GitHub - either sign up for an account or log in, if you have one - and hit the Fork button at the top right, not far from your profile image. After the repository has been created, navigate to the Log file, i.e. mine is here: https://github.com/jochenkirstaetter/100-days-of-exam/blob/master/log.md and then click on the small pencil to Edit this file. Don't be scared, the text syntax is using a technology called Markdown and the syntax isn't too hard to learn. It's just a text-based formatting syntax within the actual content. An application like LibreOffice Writer or Microsoft Word would hide that syntax from you. Then change the header line and replace [Your exam goal] with your own outcome you are looking for. Next, you could either delete the rest of the content in the file, or choose one of the existing day templates to start logging your daily progress. To complete the process you would Commit changes with a short note and hit the button to save it. And that's it for your first day. Come again tomorrow and report about your learning steps of the day. And now? Stay committed! Check out the Rules of the #100DaysOfExam challenge and prepare yourself for at least one hour each day for your exam. Block a certain time period in your calendar - make an appointment with yourself! The notification acts like a daily alarm, too. Just don't ignore it. Need motivation? Browse the hashtag on Twitter... just not for too long. \uD83D\uDE09 And while talking about calendar entries why not fix your exam date already. Give it a special place with highlighted colour. Stick to the challenge I may repeat myself here but that's hopefully okay. Keep your goal in eyesight, stick to the challenge and tweet about your progress. Response from others is likely a reward for you, too. Show yourself (and maybe others) that you are committed to your own personal growth. Prepare for your exam and document your progress. You can do this! I'm really looking forward to see your tweets popping up on my timeline, and I'm going to place my likes from time to time. Guaranteed! Last but not least: Sharing is caring! Spread the word about the #100DaysOfExam challenge even though you might not participate or prepare yourself to pass an exam. Thanks, much appreciated! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = '100-days-of-exam.html'; this.page.identifier = '9271ed1c97_id100-days-of-exam'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "101-things-you-can-do---.html": {
    "href": "101-things-you-can-do---.html",
    "title": "101 things you can do... - Get Blogged by JoKi",
    "summary": "101 things you can do... Copy Markdown View Markdown GitHub Markdown Source 101 things you can do... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '101-things-you-can-do---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '101-things-you-can-do---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '101 things you can do...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-26 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ist schon spannend, was man so findet, wenn man auf der Suche nach einem Feiertagskalendar für Sunbird ist: https://www.xulplanet.com/ndeakin/arts/reasons.html Die Übersicht ist wirklich spannend und etliche der Aspekte sind auch bereits auf Channel9 für die Anforderungen und Wünsche für den kommenden Internet Explorer 7 gelistet. Achja, noch ein kleines Zitat meines Kollegen: Irgendwie schon krass, wenn man sich das mal durchliest ... Microsoft hat echt seit ein paar Jahren gepennt ... klar, war halt keine Konkurrenz da und somit hat der Motor für die Innovation gefehlt ... da fehlt ne Menge, was eigentlich schon zu den Basics gehört ... und beim IE bekämst wohl keine 101 Sachen beisammen ... gut, bissle was mit ActiveX und den Sicherheitszonen, aber das wars auch schon ... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = '101-things-you-can-do---.html'; this.page.identifier = '9271ed1c97_id101-things-you-can-do---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "250-artikel-in-gut-2-jahren.html": {
    "href": "250-artikel-in-gut-2-jahren.html",
    "title": "250 Artikel in gut 2 Jahren? - Get Blogged by JoKi",
    "summary": "250 Artikel in gut 2 Jahren? Copy Markdown View Markdown GitHub Markdown Source 250 Artikel in gut 2 Jahren? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '250-artikel-in-gut-2-jahren'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '250-artikel-in-gut-2-jahren'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '250 Artikel in gut 2 Jahren?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-08-25 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Bei der Betrachtung der Archivsektion in der linken Navigationsleiste beschlich mich der Gedanke, dass es mal interessant wäre zu sehen, wieviele Artikel im Zeitraum seit Juli 2004 bis heute so entstanden sind... Inzwischen schreibe ich seit mehr als 2 Jahren. Wow, ich bin erfreut, dass es doch geneigte Leser geben soll, die schon sehnsüchtig auf mehr Input warten... winke-winke ins Schwabeländle \uD83D\uDE0E Well, die aktuelle Blogsoftware basiert datentechnisch auf einer MySQL Datenbank 4.irgendwas. Und in Verbindung mit dem MySQL Query Browser oder dem Fuchs ist es auch keine echte Herausforderung: Select count(\\*) From posts liefert beim Schreiben des Beitrags exakt 250. Der Vollständigkeit wegen das Ganze nun auch in Visual FoxPro: Local lnHandle lnHandle = SQLConnect( ; \"Driver={MySQL ODBC 3.51 Driver};\" + ; \"Server=jochen.kirstaetter.name;\" + ; \"Port=3306;\" + ; \"Database=blog;\" + ; \"User=blog;\" + ; \"Password=geheim;\" + ; \"Options=19035\" ; ) SQLExec(m.lnHandle, \"Select count(*) From posts\", \"posts\") Browse Last Unter der Voraussetzung, dass der MyODBC auf dem System installiert ist, ist der Zugriff auf den MySQL Server remote möglich. Auch wenn sich dieser irgendwo auf der Welt befindet. Mein Server ist beispielsweise irgendwo in Minnesota, USA untergestellt. Soweit ich mich erinnern kann... Naja, letztendlich spielt es keine Rolle, wo die Daten tatsächlich physikalisch abgelegt sind. Es hätte sich auch genauso gut um einen anderen Datenbankserver handeln können. Achja, das Attribut Options ist absichtlich so gewählt. Im Gegensatz zu den Beispielen auf ConnectionStrings.com erziele ich gegenwärtig mit dem Wert 19035 die besten Ergebnisse mit dem Fuchs. Der Zahlenwert ist übrigens die dezimale Repräsentation der Bits im Dialog Einstellungen der Verbindung des MyODBC-Treibers. Alternativ kann auch eine DSN im ODBC Administrator anlegen und die Informationen dort ablegen. Das Ergebnis sähe dann vergleichsweise so aus: Local lnHandle lnHandle = SQLConnect(\"MyBlog\", \"blog\", \"geheim\") SQLExec(m.lnHandle, \"Select count(*) From posts\", \"posts\") Browse Last Sofern die erstellte DSN unter dem Namen 'MyBlog' konfiguriert wurde. Aktuelle Informationen, Tools und Downloads zu MySQL gibt es übrigens in deren Entwicklersektion auf der Website. Vielen Dank übrigens für die zahlreichen Feedbacks, Anregungen und Ideen im Laufe der vergangenen Monate. Ich hoffe, dass ich auch künftig weiter an der Tastatur bleiben werde und euch mit zahlreichen Tipps und Tricks dienen kann. Achja, neben diesem Blog gibt es übrigens noch zwei weitere, in denen ich weitaus unregelmäßiger Artikel publiziere: Coding for fun - engl. Blog dFPUG Blog Teilweise handelt es sich um artverwandte Artikel bzw. Verweise, teilweise um vollständig autarke Informationen, die hier nicht veröffentlicht werden. Eine bunte Mischung eben. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = '250-artikel-in-gut-2-jahren.html'; this.page.identifier = '9271ed1c97_id250-artikel-in-gut-2-jahren'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "404.html": {
    "href": "404.html",
    "title": "These aren't the droids you're looking for - Get Blogged by JoKi",
    "summary": "Address Auto-Correction Redirecting in seconds... Proceed Now Stay on this page These aren't the droids you're looking for (function() { var path = window.location.pathname; var lowerPath = path.toLowerCase(); var is404Self = /^\\/(error404|404)(\\.html)?\\/?$/.test(lowerPath); // If intentionally viewing the 404 page itself, do not auto-redirect if (is404Self) { return; } var banner = document.getElementById('redirect-banner'); var titleEl = document.getElementById('redirect-title'); var messageEl = document.getElementById('redirect-message'); var redirectNowBtn = document.getElementById('redirect-now-btn'); var cancelBtn = document.getElementById('redirect-cancel-btn'); if (!banner || !titleEl || !messageEl || !redirectNowBtn || !cancelBtn) { return; } var targetUrl = ''; var secondsLeft = 10; if (path !== lowerPath) { // Case 1: Uppercase typo auto-correction targetUrl = lowerPath + window.location.search + window.location.hash; secondsLeft = 4; titleEl.textContent = 'Address Auto-Correction'; messageEl.innerHTML = 'The requested address contains uppercase characters. Redirecting to <strong>' + lowerPath + '</strong> in <strong id=\"redirect-countdown\">' + secondsLeft + '</strong> seconds...'; redirectNowBtn.textContent = 'Redirect Now'; redirectNowBtn.href = targetUrl; } else { // Case 2: Missing page -> Home recovery targetUrl = '/'; secondsLeft = 10; titleEl.textContent = 'Page Not Found'; messageEl.innerHTML = 'The page you requested could not be found. Taking you back to the <strong>Home page</strong> in <strong id=\"redirect-countdown\">' + secondsLeft + '</strong> seconds...'; redirectNowBtn.textContent = 'Go to Home Now'; redirectNowBtn.href = targetUrl; } banner.style.display = 'block'; var countdownEl = document.getElementById('redirect-countdown'); var timer = setInterval(function() { secondsLeft--; if (countdownEl) countdownEl.textContent = secondsLeft; if (secondsLeft <= 0) { clearInterval(timer); window.location.replace(targetUrl); } }, 1000); redirectNowBtn.addEventListener('click', function(e) { e.preventDefault(); clearInterval(timer); window.location.replace(targetUrl); }); cancelBtn.addEventListener('click', function() { clearInterval(timer); banner.innerHTML = '<div class=\"error-redirect-glass-inner\"><div class=\"error-redirect-info\"><p class=\"error-redirect-glass-message\">Auto-redirect cancelled. You can continue browsing below or using the search feature.</p></div></div>'; }); })(); Wooooah! Sorry, but the page you asked for does not exist or has been relocated. Do you mind using the Search feature by pressing / or Ctrl+K (or using the recovery resources below) to find what you are looking for? Agent & Crawler Recovery Resources Agent Guidance & When to Use: /llms.txt Full Machine-Readable Manifest: /llms-full.txt XML Sitemap: /sitemap.xml Raw Markdown Archive: Direct Markdown files available at /raw/<slug>.md Site Navigation Home / Latest Articles: Browse the newest published blog posts. Blog Archive: Complete historical directory of all technical articles. Topic Tags Index: Filter posts by technologies (.NET, Google AI, Tooling, Community, Architecture). About Jochen Kirstätter: Biography, Microsoft MVP & Google Developer Expert background. Contact: Direct email, social channels, and local community events. Privacy Policy: Information on data transparency, analytics, and cookies. Thanks and enjoy reading this blog. Cheers, JoKi"
  },
  "545uhr-und-der-wecker-klingelt.html": {
    "href": "545uhr-und-der-wecker-klingelt.html",
    "title": "5:45 Uhr und der Wecker klingelt - Get Blogged by JoKi",
    "summary": "5:45 Uhr und der Wecker klingelt Copy Markdown View Markdown GitHub Markdown Source 5:45 Uhr und der Wecker klingelt Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '545uhr-und-der-wecker-klingelt'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || '545uhr-und-der-wecker-klingelt'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '5:45 Uhr und der Wecker klingelt') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-02-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Oh Mann, ich muss wirklich krank oder geistig umnachtet sein, dass ich freiwillig um diese programmiererfeindliche Uhrzeit aufstehe und mich auf die Socken mache. Aber heute ist nicht irgendein Tag. Nein, ab heute heißt es auch in Deutschland offiziell Sie sind da! Jawohl, die deutschen Produkte zu Visual Studio 2005, SQL Server 2005 und BizTalk Server 2006 werden auf dem Launch-Event in Karlsruhe während der nächsten drei Tage vorgestellt. Nach meinem aktuellen Kenntnisstand sind über 4.000 Personen angemeldet... Und einer davon bin ich. \uD83D\uDE0E Aktuell befinde ich mich gerade im Zug zwischen Neustadt und Karlsruhe... Einigermaßen fit und voller Erwartungen was den heutigen Tag angeht. Ehrlich gesagt, heiße ich diese Abwechslung aus dem Programmieralltag für absolut willkommen. Auf dem letzten Stammtisch und im Forum der dFPUG wurden bereits Treffpunkte von Teilnehmern ausgemacht. Mal gespannt, wen man noch so alles in der Stadthalle antreffen wird. Aktuell weiß ich von mindestens acht Personen, dass sie da sein werden. Dank einer UMTS/GPRS Netzwerkkarte kann ich während der nächsten drei Tage wieder einmal live und direkt vom Geschehen bloggen. Mal schauen, ob nicht sogar der ein oder andere öffentliche Access-Point für WLAN in den Sessionräumen zur Verfügung steht... Nunja, wir werden sehen. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = '545uhr-und-der-wecker-klingelt.html'; this.page.identifier = '9271ed1c97_id545uhr-und-der-wecker-klingelt'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "a-star-curiously-singing-by-kerry-nietz.html": {
    "href": "a-star-curiously-singing-by-kerry-nietz.html",
    "title": "A Star Curiously Singing by Kerry Nietz - Get Blogged by JoKi",
    "summary": "A Star Curiously Singing by Kerry Nietz Copy Markdown View Markdown GitHub Markdown Source A Star Curiously Singing by Kerry Nietz Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'a-star-curiously-singing-by-kerry-nietz'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'a-star-curiously-singing-by-kerry-nietz'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'A Star Curiously Singing by Kerry Nietz') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-01-04 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Finally, I could spent some time reading during the festive days... And it was a good decision. The first science-fiction novel by Kerry Nietz already spent some time on my (virtual) bookshelf, and my inner tension to get to read that title grew over the past few weeks. Following my review, also published on Amazon: Surprisingly refreshing Welcome the cyber world of the future... After the general time change, you start your journey in the year 2000 AH (~ 2600 AD) as a debugger. You delve into the daily life of the title's protagonist, SandFly. You get an understanding of how the world has been evolved during the centuries and that the cultural background of this modern society has very deep roots. The first-person writing style of Kerry really gets you more involved into the story. The happening of events feels more intensive and surprisingly refreshing. Similar to his previous non-fictional book there are quite a good number of jokes and I had a good laugh more than once for sure. The balance between science, humanity and religious aspects is very well drawn. I'm looking forward to read more about the DarkTrench Saga soon: if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'a-star-curiously-singing-by-kerry-nietz.html'; this.page.identifier = '9271ed1c97_ida-star-curiously-singing-by-kerry-nietz'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ab-in-den-sueden.html": {
    "href": "ab-in-den-sueden.html",
    "title": "Ab in den Süden... - Get Blogged by JoKi",
    "summary": "Ab in den Süden... Copy Markdown View Markdown GitHub Markdown Source Ab in den Süden... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ab-in-den-sueden'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ab-in-den-sueden'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ab in den Süden...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-22 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich mag es ja überhaupt nicht, morgens früh aufzustehen, aber heute war mal wieder einer dieser Tage. Der Wecker foltert mich kurz nach 6:00 Uhr in der Nacht - herrje, schrecklich... Aber was soll's. Ich muss eh aufstehen, denn es geht in den Süden Deutschlands. Nach der morgendlichen Schlacht im Bad und einer erfrischend, belebenden Fahrt nach Kaiserslautern - noch 'n Kollegen einpacken - geht's pronto per Autobahnexpress Richtung Ludwigshafen, und die ersten Probleme treten auf - Verkehrsfunk und die Entscheidung welche Route wir fahren: über Heilbronn oder über Karlsruhe. Wir haben uns dann für die Baustellentour über Heilbronn entschieden und es war gut... Gerundete zwei Stunden später wir eingeflogen und konnten unser Job des Tages beginnen. Es war sehr ergiebig und produktiv - mehr werd' ich euch darüber nicht erzählen Irgendwann wird's auch Zeit für den Heimweg, und wieder die Frage der Route. Well, laut Radio sieht's diesmal auf der A81 mehr als bescheiden aus und 'fliegen' über die A8 gen Heimat. Nunja, erster Gang und Standgas - wir mussten nach einiger Zeit echt aufpassen nicht in einen Rausch der Geschwindigkeit zu fallen. Witzigerweise waren nirgends irgendwelche Anzeichen von Verkehrsunfall oder sonstwas Vergleichbarem... \"Hey, hier ist gar kein Stau...\" brems - Nunja, ich lass' mich mal nicht über diverse Fahrstile aus, aber es war wieder mal sehr amüsant, wenn irgendwelche Teilnehmer von der Auffahrt in einem Rutsch quer über 3 Spuren auf die linke Spur zerren - ohne Rücksicht auf Verluste... Und das war der Trip in den Süden... Deutschlands. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ab-in-den-sueden.html'; this.page.identifier = '9271ed1c97_idab-in-den-sueden'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "about.html": {
    "href": "about.html",
    "title": "About Me - Get Blogged by JoKi",
    "summary": "About Me Copy Markdown View Markdown GitHub Markdown Source About Me Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'about'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'about'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'About Me') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); \"The only frontiers are in your mind\" Hi, my name is Jochen Kirstätter, also known as \"JoKi\". I'm a senior software crafter, blogger, community founder, and speaker. I have been awarded as Microsoft Most Valuable Professional (MVP) for Developer Technologies and as Google Developer Expert (GDE) for Cloud and AI. These awards are a recognition of my community contributions, my passion to share knowledge, and my activities to engage with their respective services and products over years. Bio Although living on a tropical island, Jochen Kirstätter is a business owner and entrepreneur in different industry sectors. Mainly operating in the development of tailor-made software solutions since more than 20 years, he is also venturing into the world of cloud computing. You can either meet him at the regular meetups of the Mauritius Software Craftsmanship Community or reach him easily on X (prev. Twitter) @JKirstaetter, BlueSky @jochen.kirstaetter.name, and Mastodon @JKirstaetter. Jochen is a Google Developer Expert (GDE) for Google Cloud and AI; he is also a Microsoft MVP for Developer Technologies for several years. He founded the now largest IT community in Mauritius, and organises the annual Developers Conference. Following his interest in cross-platform technologies he is also an organiser of the Google Developer Group (GDG) Mauritius. Headshot Photo Mauritius Software Craftsmanship Community The MSCC was founded back in May 2013 when my daily job as well as my private life allowed me to have a bit of spare time, and my passion to share knowledge and information with like-minded geeks came together. Since then the community has grown as the largest and most active user group in Mauritius. We organise the annual Developers Conference, run monthly meetings on various topics, and constantly encourage young and old people to venture into the world of information technology. Google Developer Group Mauritius The GDG Mauritius was an idea that kept me thinking about since 2015. During the first annual Developers Conference I was hoping that there would be other Google geeks to probably kick off a local GDG chapter. Unfortunately, it do not happen back then. Back in June 2017 I applied for and founded the GDG Mauritius given that my interest in Google technologies was ever increasing and I felt the need to balance the emphasis on Microsoft technologies. Ways of Contact I would love it if you would subscribe to my blog's RSS feed. I'm also on X, BlueSky, Mastodon, LinkedIn, and Facebook. If you just want to email me, go ahead send an email to jochen@kirstaetter.name but be nice and make sure you questioned the interweb a bit before you do."
  },
  "accessing-apache2-web-server-via-ipv6.html": {
    "href": "accessing-apache2-web-server-via-ipv6.html",
    "title": "Accessing your web server via IPv6 - Get Blogged by JoKi",
    "summary": "Accessing your web server via IPv6 Copy Markdown View Markdown GitHub Markdown Source Accessing your web server via IPv6 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'accessing-apache2-web-server-via-ipv6'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'accessing-apache2-web-server-via-ipv6'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Accessing your web server via IPv6') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-03-16 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Being able to run your systems on IPv6, have automatic address assignment and the ability to resolve host names are the necessary building blocks in your IPv6 network infrastructure. Now, that everything is in place it is about time that we are going to enable another service to respond to IPv6 requests. The following article will guide through the steps on how to enable Apache2 httpd to listen and respond to incoming IPv6 requests. This is the fourth article in a series on IPv6 configuration: Configure IPv6 on your Linux system DHCPv6: Provide IPv6 information in your local network Enabling DNS for IPv6 infrastructure Accessing your web server via IPv6 Piece of advice: This is based on my findings on the internet while reading other people's helpful articles and going through a couple of man-pages on my local system. Surfing the web - IPv6 style Enabling IPv6 connections in Apache 2 is fairly simply. But first let's check whether your system has a running instance of Apache2 or not. You can check this like so: $ service apache2 status Apache2 is running (pid 2680). In case that you got a 'service unknown' you have to install Apache to proceed with the following steps: $ sudo apt-get install apache2 Out of the box, Apache binds to all your available network interfaces and listens to TCP port 80. To check this, run the following command: $ sudo netstat -lnptu | grep \"apache2\\W*$\" tcp6 0 0 :::80 :::* LISTEN 28306/apache2 In this case Apache2 is already binding to IPv6 (and implicitly to IPv4). If you only got a tcp output, then your HTTPd is not yet IPv6 enabled. Check your Listen directive, depending on your system this might be in a different location than the default in Ubuntu. $ sudo nano /etc/apache2/ports.conf # If you just change the port or add more ports here, you will likely also # have to change the VirtualHost statement in # /etc/apache2/sites-enabled/000-default # This is also true if you have upgraded from before 2.2.9-3 (i.e. from # Debian etch). See /usr/share/doc/apache2.2-common/NEWS.Debian.gz and # README.Debian.gz NameVirtualHost *:80 Listen 80 <IfModule mod_ssl.c> # If you add NameVirtualHost *:443 here, you will also have to change # the VirtualHost statement in /etc/apache2/sites-available/default-ssl # to <VirtualHost *:443> # Server Name Indication for SSL named virtual hosts is currently not # supported by MSIE on Windows XP. Listen 443 </IfModule> <IfModule mod_gnutls.c> Listen 443 </IfModule> Just in case that you don't have a ports.conf file, look for it like so: $ cd /etc/apache2/ $ fgrep -r -i 'listen' ./* And modify the related file instead of the ports.conf. Which most probably might be either apache2.conf or httpd.conf anyways. Okay, please bear in mind that Apache can only bind once on the same interface and port. So, eventually, you might be interested to add another port which explicitly listens to IPv6 only. In that case, you would add the following in your configuration file: Listen 80 Listen [2001:db8:bad:a55::2]:8080 But this is completely optional... Anyways, just to complete all steps, you save the file, and then check the syntax like so: $ sudo apache2ctl configtest Syntax OK Let's apply the modifications to our running Apache2 instances: $ sudo service apache2 reload * Reloading web server config apache2 ...done. $ sudo netstat -lnptu | grep \"apache2\\W*$\" tcp6 0 0 2001:db8:bad:a55:::8080 :::* LISTEN 5922/apache2 tcp6 0 0 :::80 :::* LISTEN 5922/apache2 There we have two daemons running and listening to different TCP ports. Now that the basics are in place, it's time to prepare any website to respond to incoming requests on the IPv6 address. Open up any configuration file you have below your sites-enabled folder. $ ls -al /etc/apache2/sites-enabled/ ... $ sudo nano /etc/apache2/sites-enabled/000-default <VirtualHost *:80 [2001:db8:bad:a55::2]:8080> ServerAdmin webmaster@ios.mu ServerName server.ios.mu ServerAlias server Here, we have to check and modify the VirtualHost directive and enable it to respond to the IPv6 address and port our web server is listening to. Save your changes, run the configuration test and reload Apache2 in order to apply your modifications. After successful steps you can launch your favourite browser and navigate to your IPv6 enabled web server. Accessing an IPv6 address in the browser That looks like a successful surgery to me... Note: In case that you received a timeout, check whether your client is operating on IPv6, too. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'accessing-apache2-web-server-via-ipv6.html'; this.page.identifier = '9271ed1c97_idaccessing-apache2-web-server-via-ipv6'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "addthis-google-analytics-asynchronous-tracking-code-integration.html": {
    "href": "addthis-google-analytics-asynchronous-tracking-code-integration.html",
    "title": "AddThis and Google Analytics : Asynchronous Tracking Code Integration - Get Blogged by JoKi",
    "summary": "AddThis and Google Analytics : Asynchronous Tracking Code Integration Copy Markdown View Markdown GitHub Markdown Source AddThis and Google Analytics : Asynchronous Tracking Code Integration Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'addthis-google-analytics-asynchronous-tracking-code-integration'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'addthis-google-analytics-asynchronous-tracking-code-integration'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'AddThis and Google Analytics : Asynchronous Tracking Code Integration') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-03-03 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); During the weekend I did some modifications, read improvements, here on the website according to Search Engine Optimization (SEO), keywords and page load times. Reducing the page load times made it necessarily to arrange my Javascript snippets in the template. So far, everything went well, until I figured out that my Google Analytics dropped down to 0... So, while checking those issues I stumbled upon the 'new' Google Analytics Asynchronous Tracking code. This will also reduce page load time as \"asynchronous tracking optimizes how browsers load ga.js so its impact on user experience is minimized.\" - Great! The steps to use the asynchronous tracking is straight forward: Memorize or copy your web property ID from the existing snippet Remove the synchronous tracking code Insert the asynchronous tracking code <script type=\"text/javascript\"><!-- var _gaq = _gaq || []; _gaq.push(['_setAccount', 'UA-XXXXX-X']); _gaq.push(['_trackPageview']); (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'https://www') + '.google-analytics.com/ga.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga); })(); // --></script> Replacing UA-XXXXX-X with your web property ID. That's the easy part... Integrating AddThis into the 'standard' Google Analytics is extensively described on the help pages of AddThis. But sadly, there is no information about the asynchronous tracking code. Additionally, I checked the AddThis support forum and the topic 'Google Analytics : Asynchronous Tracking Code Integration' is discussed since a while. Not even answered by AddThis supporters yet. Even the Google Forum does not provide an answer. I was astonished to see this! Frankly speaking, this is not an unusual situation for a website, or? Anyways, back to this article. You can send AddThis shares to your Google Analytics reports as custom events in the category “addthis” by adding the following configuration code to your existing AddThis sharing code: addthis_config = { data_ga_tracker: pageTracker } The main problem about the interoperability between AddThis and Google Analytics is that the asynchronous tracking code does not provide a pageTracker object anymore. As said, this only works with the synchronous tracking code of Google Analytics. The forum threads actually provides some directions towards the solution of this problem. The link between those two services is to reference the GA pageTracker object in the AddThis configuration. The Asynchronous Tracking Usage Guide provides the necessary information in the paragraphs about Multiple Tracker Objects and Pushing Functions. It is not clearly described on the spot but with a little bit of logic you can figure it out: Create your own Javascript variable that queries the Google API. _gaq.push(function() { var pageTracker = _gaq._getAsyncTracker('myTracker'); var link = document.getElementById('my-link-id'); link.href = pageTracker._getLinkerUrl('https://example.com/'); }); < The solution lies in the parameter of _getAsyncTracker(). The samples in the User Guide refer to a named tracker. But what about the initial one? Right, just specify an empty string and you are done! To summarize this article just use the following code snippet at the very end of your website to integrate Google Analytics : Asynchronous Tracking with your AddThis analytics: <!-- Google Analytics and AddThis button --> <script type=\"text/javascript\"> (function() { var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true; ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'https://www') + '.google-analytics.com/ga.js'; (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(ga); })(); </script> <script type=\"text/javascript\"> **var pageTracker = []; if (_gaq._getAsyncTracker) { pageTracker = _gaq._getAsyncTracker(''); }** var addthis_config = { data_ga_tracker: pageTracker };</script> </body> The GA user guide recommends to split your asynchronous code over your HTML content. The setup of the tracker should be directly after the <body> tag whereas the rest should be placed at the very end of your document (as described above). Hopefully, my description is clear enough to enjoy a smooth and seamless code integration between Google Analytics Asynchronous Tracking and AddThis Sharing. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'addthis-google-analytics-asynchronous-tracking-code-integration.html'; this.page.identifier = '9271ed1c97_idaddthis-google-analytics-asynchronous-tracking-code-integration'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "adjust-timezone-of-an-avm-fritzbox-7390.html": {
    "href": "adjust-timezone-of-an-avm-fritzbox-7390.html",
    "title": "Adjust timezone of an AVM Fritz!Box 7390 - Get Blogged by JoKi",
    "summary": "Adjust timezone of an AVM Fritz!Box 7390 Copy Markdown View Markdown GitHub Markdown Source Adjust timezone of an AVM Fritz!Box 7390 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'adjust-timezone-of-an-avm-fritzbox-7390'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'adjust-timezone-of-an-avm-fritzbox-7390'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Adjust timezone of an AVM Fritz!Box 7390') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-07-30 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It's been a while that I purchased an AVM Fritz!Box 7390 but since I'm using this 'PABX' here in Mauritius, I'm not really happy about the wrong time in the logs or handsets connected. Lately, I had some spare time to address this issue, and the following article describes how to adjust the timezone settings in general. The original idea came from an FAQ found in c't 21/11 (for a 7270 written in German language) but I added a couple of things based on other resources online. The following tutorial may be valid for other models, too. Use your common sense and think before you act. Brief introduction to AVM Fritz!Box devices The Fritz!Box series of AVM has been around for more than a decade and those little 'red boxes' have a high level of versatility for your small office or home. High-speed connections, secure WLAN and convenient telephony make a home network out of any network. Whether it's a computer, tablet or smartphone, any device can be connected to the FRITZ!Box. And best of all, installation is so simple that users will be online in a matter of minutes. If you want to have peace of your mind in your small network then a Fritz!Box is the easiest way to achieve that. I'm using my box primarly as WiFi access point, VoIP gateway and media server but only because it came in second after my Linux system. Limitations in the administrative Web UI Unfortunately, there are no possibilities to adjust the timezone settings in the Web UI at all - even not in Expert mode. I assume that this is part of the 'simplification' provided by AVM's design team. That's okay, as long as you reside in Central Europe, and the implicit time handling is correct for your location. Adjusting the timezone I got my device through an order at Amazon Germany already some time ago, and honestly I wasn't bothered too much about the pre-configured (fixed) timezone setting - CET or CEST depending on daylight saving. But you know, it's that kind of splinter at the back of your head that keeps nagging and bothering you indirectly. So, finally I sat down yesterday evening and did a quick research on how to change the timezone. Even though there are a number of results, I read the FAQ from the c't magazine first, as I consider this as a trusted and safe source of information. Of course, it is most important to avoid to 'brick' your device. You've been warned - No support Tinkering with the configuration of any AVM devices seems to be a violation of their official support channels. So, be warned and continue only in case that you're sure about what you are going to do. The following solutions are 'as-is' and they worked for my box flawlessly but may cause an issue in your case. Don't blame me... Solution 1 - Backup, modify and restore That's the way as described in the c't article and a couple of other forum postings I found online, mainly from Australia. Login the administrative Web UI and navigate to 'System => Einstellungen sichern' (System => Backup configuration) and store your current configuration to a local file on your machine. Despite some online postings it is not necessary to specify a password in order to secure or encrypt your backup. IMHO, this only adds another unnecessary layer of complexity to the process. Anyway, next you should create a another copy of your settings and keep it unmodified. That's our safety net to restore the current settings in case that we might have to issue a factory setting reset to the box. Now, open the configuration file with an advanced text editor which is capable to deal with Unix carriage returns properly - Windows Notepad doesn't do the job but Wordpad or Notepad++. Personally, I don't care and simply use geany, gedit or nano on Linux. In total there are 3 modifications that we have to apply to the configuration file - one new line and two adjustments. First, we have to add an instruction near the top of file that overrides the device internal checksum validation. Without this line, your settings won't be accepted. Caution: The directives are case-sensitve and your outcome should read something like this: **** FRITZ!Box Fon WLAN 7390 CONFIGURATION EXPORT Password=$$$$<ignore> FirmwareVersion=84.05.52 CONFIG_INSTALL_TYPE=iks_16MB_xilinx_4eth_2ab_isdn_nt_te_pots_wlan_usb_host_dect_64415 OEM=avm Country=049 Language=de NoChecks=yes **** CFGFILE:ar7.cfg /* * /var/flash/ar7.cfg * Mon Jul 29 10:49:18 2013 */ ar7cfg { ... Then search for the expression 'timezone' and you should find a section like this one (~ line 1113): timezone_manual { enabled = no; offset = 0; dst_enabled = no; TZ_string = \"\"; name = \"\"; } We would like to manually handle the timezone setting in our device and therefore we have to enable it and set the proper value for Mauritius. The configuration block should like so afterwards: timezone_manual { enabled = yes; offset = 0; dst_enabled = no; TZ_string = \"MUT-4\"; name = \"\"; } We specify the designation and the offset in hours of the timezone we would like to have. Caution: The offset indicates the value one has to add to the local time to arrive at UTC. More details are described in the Explanation of TZ strings. Mauritius has GMT+4 which means that we have to substract 4 hours from the local time to have UTC. Finally, we restore the modified configuration file via the administrative Web UI under 'System => Einstellungen sichern => Wiederherstellen' (System => Backup configuration => Restore). This triggers a reboot of the device, so please be patient and wait until the Web UI displays the login dialog again. Good luck! Solution 2 - Telnet Update #1: Thanks to a comment made by Frank it should be noted that Telnet service seems to be removed starting firmware 6.30. A more elegant, read: technically interesting, way to adjust configuration settings in your Fritz!Box is to access it directly through Telnet. By default AVM disables that protocol channel and you have to enable it with a connected telephone. In order to activate the telnet service dial the following combination: #967 #968 (to disable telnet again after work has been completed) If you're using an AVM handset like the Fritz!Fon then you will receive a confirmation message on the display like so: telnetd ein Next, depending on your favourite operating system, you either launch a Command prompt in Windows or a terminal in Linux, get your Admin password ready, and you connect to your box like so: $ telnet fritz.box Trying 192.168.1.1... Connected to fritz.box. Escape character is '^]'. password: BusyBox v1.19.3 (2012-10-12 14:52:09 CEST) built-in shell (ash) Enter 'help' for a list of built-in commands. ermittle die aktuelle TTY tty is \"/dev/pts/0\" Console Ausgaben auf dieses Terminal umgelenkt That's it, you are connected and we can continue to change the configuration manually. In order to adjust the timezone setting we have to open the ar7.cfg file. As we are now operating in a specialised environment, we only have limited capabilities at hand. One of those is a reduced version of vi - nvi. Let's open a second browser window with the fine manual page of nvi and start to edit our configuration file: nvi /var/flash/ar7.cfg In our configuration file, we have to navigate to the timezone directives. The easiest way is to search for the expression 'timezone' by typing in the following: /timezone (press Enter/Return) Now, we should see the exact lines of code like in the backed up version: timezone_manual { enabled = no; offset = 0; dst_enabled = no; TZ_string = \"\"; name = \"\"; } And of course, we apply the same changes as described in the previous section: timezone_manual { enabled = yes; offset = 0; dst_enabled = no; TZ_string = \"MUT-4\"; name = \"\"; } Finally, we have to write our changes back to the file and apply the new settings. :wq (press Enter/Return) ar7cfgchanged That's it! Finally, close the telnet session by pressing Ctrl+] and enter 'quit'. Additional ideas... There are a couple of more possibilities to enhance and to extend the usability of a Fritz!Box. There are lots of resources available on the net, but I'd like to name a few here. Especially for Linux users it is essential to be able to connect to any device remotely in a safe and secure way. And the installation of a SSH server on the box would be a first step to improve this situation, also to avoid to run telnet after all. Sometimes, there might be problems in your VoIP connections, feel free to adjust the settings of codecs and connection handling, too. I guess, you'll get the idea... The only frontiers are in your mind. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'adjust-timezone-of-an-avm-fritzbox-7390.html'; this.page.identifier = '9271ed1c97_idadjust-timezone-of-an-avm-fritzbox-7390'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "afpwiki-core-engine-available.html": {
    "href": "afpwiki-core-engine-available.html",
    "title": "AfpWiki Core Engine available - Get Blogged by JoKi",
    "summary": "AfpWiki Core Engine available Copy Markdown View Markdown GitHub Markdown Source AfpWiki Core Engine available Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'afpwiki-core-engine-available'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'afpwiki-core-engine-available'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'AfpWiki Core Engine available') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-06 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hi, just published the core engine of AfpWiki. The code is reuseable for own purposes - whether it is web development or desktop development... Message from news://news.prolib.de/prolib.public.afp.english Hello AFP coderz, I'd like to share my code fragments of the AfpWiki core engine with you all. Attached to this message you'll receive a ZIP archive with a sample and procedure file to run AfpWiki comparable solutions. It's a standalone sample and will be a sample of future releases of AFP. Just drop it into a web folder and call afpwiki.afp in your browser. This sample requires HTML Plugin for Active FoxPro Pages. If you're going to integrate the code in your existing AFP applications you have to do these steps: Include the procedure file into your AFP application ([xxx.afpa.code] or [xxx.afp.code]): *!<[INCLUDE: \"afpwiki.code\"]> Call the function wikinize: m.lcValue = Wikinize(m.lcValue) The core engine is just the break down core engine of AfpWiki. It has no storage or versioning capabilities as AfpWiki provides. I would be very nice if you send in any questions and improvements on the code. Either send replies to this thread, or drop me a mail at jochenk@prolib.de / joki@afpfaq.de Disclaimer: The code is provided \"as-is\" and you are using it at your own risk. \uD83D\uDE09 Kind regards, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'afpwiki-core-engine-available.html'; this.page.identifier = '9271ed1c97_idafpwiki-core-engine-available'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "afpwiki-released.html": {
    "href": "afpwiki-released.html",
    "title": "AfpWiki released - Get Blogged by JoKi",
    "summary": "AfpWiki released Copy Markdown View Markdown GitHub Markdown Source AfpWiki released Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'afpwiki-released'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'afpwiki-released'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'AfpWiki released') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-24 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Today is the day! Zwar bereits seit gestern online geschaltet, so ist doch heute erst der offizielle Launch des AfpWiki vollzogen. Nach einem kleinen Chat per MSN mit Peter Herzog, dem ursprünglichen Entwickler des AfpWiki kamen wir zur Entscheidung, dass das AfpWiki ab jetzt OpenSource ist und die Weiterentwicklung in der Community vollzogen werden darf. Hier meinen Dank an Peter, Danke! Natürlich hab ich diese Information nicht nur in meinem Weblog veröffentlicht, sondern ebenfalls in den gängigen Info-Quellen und Tummelplätzchen der VFP-Szene... Ich hoffe, dass ein wenig Resonanz auf dieses Release kommen wird. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'afpwiki-released.html'; this.page.identifier = '9271ed1c97_idafpwiki-released'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "afpwiki-wchst-weiter.html": {
    "href": "afpwiki-wchst-weiter.html",
    "title": "AfpWiki wächst weiter... - Get Blogged by JoKi",
    "summary": "AfpWiki wächst weiter... Copy Markdown View Markdown GitHub Markdown Source AfpWiki wächst weiter... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'afpwiki-wchst-weiter'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'afpwiki-wchst-weiter'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'AfpWiki wächst weiter...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-13 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Auch hier gibt's inzwischen wieder ein paar Neuigkeiten. Leider fehlt mir schlicht weg die Zeit (und der Server), das AfpWiki wieder live zu schalten. Aber dennoch ist es weiterhin im täglichen Einsatz und in der Weiterentwicklung. Hier kurz die Neuerungen: Neuerungen MSDN Webcast: Durch die Kombination von WC kann auf MSDN Webcasts referenziert werden. Gleiches Prinzip wie bei den Verweisen auf die KnowledgeBase. AfpWikiMakros: In der Hilfe sind die vorhandenen Makros (primär für Zeitstempel) dokumentiert. C#: HtmlPlugin für SyntaxHighlighting in C# erweitert. Weitere Programmiersprachen mit Schlüsselwörter können tabellenbasiert mit neuen Releases des Plugin hinzugefügt werden. Quellcode: Nach etlichem Warten endlich Quellcodeinterpretation mit SyntaxHighlighting. Weitere Infos stehen in der Hilfe. SIP-Rufnummer: Neben der Skypekennung werden nun auch SIP-Kennungen interpretiert und verlinkt. Leider gibt's noch keinen ProtocolHandler in Windows, der sip auf eine Anwendung bindet. Neuer Eintrag: Umstellung von JavaScript auf HTML-basierte Lösung zur Erstellung eines neuen Eintrags. Verbesserungen am Editor: Primär ist die Funktionsweise der Toolbar korrigiert und es gibt eine Toolbar für die Smilies. Fehlerbehebung Fehlerbehebung: Wörter können nun mit beliebigen Satzzeichen enden. Das InfoSystem, das permanent weiterentwickelt wird, verwendet WikiWords... Und Visual Foxpro?!? Außerdem werden die ersten Pluralformen korrekt erkannt und auf den Singular verlinkt. Bsp.: Fehler bei den BugReporten eintragen. Fehlerbehebung: Formatierungserkennung verbessert. Optik der RecentChanges verbessert. Fehlerbehebung: Probleme bei Hyperlinks mit integrierten WikiWords behoben. Auf den ersten Blick ist es nicht viel, aber einige Dinge erwiesen sich im Alltag als störend. Mal schauen, wann ich endlich die Freizeit finde, ein Release (evtl. mit Setup) zu erzeugen. In der aktuellen Situation sieht's ungünstig aus, da ich noch mitten in den Vorbereitungen für die Devcon in Frankfurt stecke. Vielleicht bringt's was, wenn jemand von außen fordert... Dunno. \uD83D\uDE0E Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'afpwiki-wchst-weiter.html'; this.page.identifier = '9271ed1c97_idafpwiki-wchst-weiter'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "aiip-azure-devops.html": {
    "href": "aiip-azure-devops.html",
    "title": "Guest lecturer for the Africa Industrial Internet Programme (AIIP) - Get Blogged by JoKi",
    "summary": "Guest lecturer for the Africa Industrial Internet Programme (AIIP) Copy Markdown View Markdown GitHub Markdown Source Guest lecturer for the Africa Industrial Internet Programme (AIIP) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aiip-azure-devops'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aiip-azure-devops'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Guest lecturer for the Africa Industrial Internet Programme (AIIP)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-09-06 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); As an active member in the IT communities of Mauritius it seizes to amaze me to see the increasing number of events and activities happening. Some of the local universities started to look out for guest lecturers to share their professional experience with their students. Mid of August I received an email from the African Leadership University to participate as a guest speaker in their adult training program called Africa Industrial Internet Programme (AIIP). Particularly, to talk during one of their webinar intensives. This is a year-long post-graduate programme developed by ALU and powered by GE to address the demand for a digital-savvy workforce in the increasingly data-driven industrial organisations through offering modules in machine learning, big data analytics, the Industrial Internet of Things (IIoT) and cloud-based application development. That webinar is for the scholars who are going to start the Application Development module. The students will have to use the skills they have learned in Machine Learning and Big Data to build applications. The targeted cloud computing platform would be Microsoft Azure. These intensives are usually 90 mins long and highly interactive. My session was centred around the following topic: Application Development - deploying machine learning, development on Cloud platforms such as Azure, DevOps etc. Wonderful, and surely an area to share quite some information with the attendees. Running the webinar Unfortunately due to a little misunderstanding on my side, I initially drove to the African Leadership Campus (ALC) where we hosted the DevFest Mauritius 2018. But I was expected at the premises of the university that resides in the former facilities of a sugar cane factory. Luckily, both locations are less than 2 minutes drive away from each other and we could start the session within the academic quarter. The AIIP uses Zoom to run their webinar intensives. Good, I have already attended a few sessions on Zoom, mainly as attendee, and with the client application installed I managed to quickly join. Shivanand introduced me quickly to the approximately 35 participants and after a few questions to the audience I launched into Microsoft Azure. As it turned out most attendees did not have access to Azure (yet) I showed them briefly the sign up process for the free account. Then, I logged into the Azure portal using my credentials and gave a short tour of the Home page and the Dashboard area. One or two questions came up in regards to the customisation and management of the dashboard which is very flexible and adjustable. Next, I moved forward to Create a resource from the sidebar navigation and showed the various options in the Compute section of the Azure Marketplace, as shown below. By searching the Marketplace I made it obvious that there are way more possibilities offered than just the featured ones. Starting with a familiar approach of running a virtualized environment in Azure I guided the attendees through the steps of creating a VM with an OS of their choice. Of course, the VM had to be located in the newly available region South Africa North. However, I also explained that certain capacities, sizes of VMs, or other enhanced features can have regional restrictions. Those limits are bound to the capabilities the selected data centre(s) are able to offer. Speaking of VM size we clicked on Change size to open the blade with the table of potential options. I also mentioned that the family of a VM defines the ratio between vCPUs, RAM and data disks. More details on the estimated monthly cost can be found in the pricing calculator of Azure products. While the new VM was provisioned I used the waiting time to show some additional configuration settings of an existing VM. Here, I explained that it is probably better to allocate a DNS name to the VM. Using a DNS name makes it easier to access the instance, like ie. via Remote Desktop Protocol (RDP) or HTTP/S if a web server is running. The activation of the auto-shutdown feature is highly recommended in case that you would run a VM only for a few hours. One of my VMs is specifically configured to give my children easy access to Microsoft Office and Paint.net from their Linux-based laptop. That VM is configured to shutdown automatically at 19:00 hrs every day because of dinner time, and to avoid cutting a whole into my wallet. A VM in the cloud might be easiest option to start with but moving forward I explained the participants the advantages of using an Azure App Service as Platform-as-a-Service (PaaS) to deploy an application. App Service has different technology stacks and corresponding runtime versions to choose from. Using an existing app service I covered the general settings and answered a few questions. Then I explained the ability to set up own custom domains for an app service. Following some questions regarding scaling we had a look at the App Service plan and how both options - Scale up and Scale out - can be handled. Next, I demonstrated the use of Deployment slots and how easy it is to have multiple variants of the same application at your finger tips. As I was about to move on to the next option of application development on Azure we got surprised by power outage in the building, interestingly the Wi-Fi and internet connection remained active, followed by fire alarm. Luckily, it was a false alarm but somehow the flow of the webinar had been interrupted harshly. The webinar attendees had some more questions on the Azure services and features shown so far, and we decided to elaborate a little bit more in those areas. More options to deploy to Azure The following options were not part of the webinar but I promised the attendees that I would write them down in this blog article. Azure has more than 100 services in the Marketplace. In regards to application development, deployment of machine learning, cognitive services, DevOps, and so on I am going to give a small selection here: Logic App Azure Functions Azure Kubernetes Service (AKS) DevOps Project Azure Bot Service Language Understanding (LUIS) Talking about all those services within a 90-minute webinar isn't sufficient. Each individual service has enough material to talk about and run demos for days. I would assume that it was more important to the webinar attendees to have a chance to ask their questions on everything Azure at this stage than to get a detailed insight of a particular service. Additional learning resources To wrap up this article and the AIIP webinar on application development on Azure I would like to mention the following platforms. They provide free access to products and services by Microsoft which will be handy for the current module: Visual Studio Essentials provides you with downloads of latest versions Visual Studio and Visual Studio Code as well as few other perks after you signed up with your Microsoft account. Azure DevOps gives you access to a full-featured source code version control system (based on git including private repositories), the ability to track your work with modern project management options like Kanban boards, backlogs, team dashboards, etc., an open platform for continuous integration and continuous deployment (CI/CD) of your applications, and free minutes for automatic test plans. Microsoft Learn has free online training courses on Azure to offer. There are various Azure learning paths available. The content on Learn is also focused towards the new role-based certifications on Azure. Free Pluralsight courses in partnership with Microsoft. Using this offer gives you access to over 40 high quality video courses on Pluralsight. This specialised subscription is valid until end of 2024. And the recently added Role IQ assessments are a good preparation for all role-based Azure certifications. Microsoft Hands-on Labs lists over 150 self-paced labs to explore and practice with. Acquire the cloud skills you need, at your own pace. Enjoy hands-on learning on your schedule with our free, self-paced labs, and keep your cloud knowledge fresh. Image credit: Headway if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'aiip-azure-devops.html'; this.page.identifier = '9271ed1c97_idaiip-azure-devops'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ajax-der-schnelle-kchenreiniger-fr-die-website.html": {
    "href": "ajax-der-schnelle-kchenreiniger-fr-die-website.html",
    "title": "AJaX - der schnelle Küchenreiniger für die Website - Get Blogged by JoKi",
    "summary": "AJaX - der schnelle Küchenreiniger für die Website Copy Markdown View Markdown GitHub Markdown Source AJaX - der schnelle Küchenreiniger für die Website Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ajax-der-schnelle-kchenreiniger-fr-die-website'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ajax-der-schnelle-kchenreiniger-fr-die-website'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'AJaX - der schnelle Küchenreiniger für die Website') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-01-26 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Zumindest war das mein erster Gedanke nachdem ich von einem Kollegen einen Überblick über Asynchronous JavaScript and XML (AJaX) vermittelt bekomme habe. Der aktuelle Hype um diesen Kommunikationsansatz zwischen Client (in Form des Browsers) und Server (beliebiger Webserver) scheint mir auf alle Fälle mal wieder ziemlich aufgeblasen. Wenn man bedenkt, dass AJaX eigentlich schon seit 1998 Bestand in der Webentwicklung hat, nunja, Guten Morgen Webentwickler! Microsoft stellte in Verbindung mit dem Outlook Web Access (OWA) die Kombination von JScript/VBScript und HTML/XML erstmalig vor. Die Verbreitung dieses wirklich coolen Arbeitsumfeldes wurde jedoch bedingt durch die eingesetzte ActiveX-Technologie, der Reduzierung auf die Windowsplattform und damit dem Internet Explorer, sowie den fast täglichen Schlagzeilen zu Sicherheitslücken bei ActiveX-Komponenten in Webseiten extremst in den Hintergrund gedrängt. Wirklich schade... Der Dreh- und Angelpunkt bei AJaX ist ein clientseitiges Objekt, welches im Hintergrund die Kommunikation mit dem Webserver managen kann, ohne dass durch die abgesetzten Requests und Responses die komplette Webseite neu im Browser aufgebaut werden muss. Mittels Frames kann man zumindest annähernd einen vergleichbaren Effekt erzeugen, da hier nur Teile der optischen Darstellung erneut geladen werden. Und um den Bogen zurück zu ActiveX-Control hinzubekommen, wird für diese Kommunikation ein XMLHttpRequest-Objekt instanziert und genutzt. Und damit erschliesst sich auch das Problem der Vergangenheit: Nur der IE stellte dieses Objekt zur Verfügung. Inzwischen sprach sich auch bei den anderen Browserherstellern herum, dass dieses XMLHttpReqeust-Objekt für die coolen Aktionen innerhalb einer HTML-Seite einen großen Vorteil darstellt. Somit es gegenwärtig problemlos möglich mit den gängigen Browsern AJaX-Lösungen zu programmieren. Aber warum sollte man überhaupt AJaX für die Webentwicklung verwenden? Gute Frage, die ich mit einem kleinen Beispiel anführen möchte. Nehmen wir folgende Situation / Aufbau für eine HTML-Darstellung an: Eine zweigeteilte Website mit einer Liste aller Kunden auf der linken Seite und den Detailinformationen des selektierten Kunden in mehreren Controls auf der rechten Seite. An für sich keine schwierige Anforderung und ebenfalls ein geläufiger Aufbau in Desktopanwendungen. Durch die verbindungslose Kommunikation zwischen Client und Server stehen wir nun vor dem Problem, was eigentlich passiert, wenn der Kunde in der Auswahlliste geändert wird. Im klassischen Sinne würde dies bedeuten, dass ein Request an den Webserver abgesetzt wird und anschliessend die Seite neu aufgebaut wird, da sich die anzuzeigenden Informationen im Detailbereich geändert haben. Naja, nicht gerade prickelnd, wenn man bedenkt, dass vielleicht nur 5% der visuellen Darstellung zu ändern sind. Die Alternative könnte darin bestehen, dass bereits beim ersten Aufruf der Kundenliste alle Detailinformationen geliefert werden. Nunja, bei einer geringen Anzahl macht das vielleicht noch Spass, aber je mehr Kunden vorliegen desto länger dauert der Aufbau. Ein Zugriff über eine schmale Leitung wird hierbei für eine Kaffeepause sorgen. Und genau hier bietet AJaX die geschickteste Lösung. Nehmen wir wieder unsere Kundenliste her. Alle Einträge haben im Normalfall einen eindeutigen Identifier - den sogenannten Primärschlüssel. Durch den Einsatz des XMLHttpRequest-Objekt, einer speziellen Seite auf dem Webserver und dem Primärschlüssel des gewählten Kunden sind wir in der Lage die Detailinformationen en demand abzurufen und weiter zu verarbeiten. Über das XMLHttpRequest-Objekt setzen wir einen asynchronen Request auf den Webserver ab, welcher uns die Detaildaten des Kunden beispielsweise in XML zurücksendet. Die komplette Verarbeitung erfolgt clientseitig mittels JavaScript. Und damit wäre die Namensgebung für AJaX erklärt. \uD83D\uDE01 Kurzfassung: Anstelle eines Anchor lösen wir eine Funktionalität in JavaScript aus, die das Ergebnis in die bestehende HTML-Seite einarbeitet. Genug der trockenen Theorie schauen wir uns die Sache mal in der Praxis an. Zunächst einmal die fertige HTML-Seite mit der Kundenliste und den Detaildaten des selektierten Kunden. Für die Aktualisierung als Reaktion auf den Wechsel des Kunden nutzen wir die Ereignisverarbeitung von JavaScript. Die Liste bietet uns ein Ereignis onchange() an, und hieran binden wir uns, um unsere eigene Routine laufen zu lassen. Zum Absetzen des Request benötigen wir erst eine gültige Instanz eines XMLHttpRequest-Objekt. Wir berücksichtigen hierbei gleich die unterschiedlichen Implementierungen der Browser und erzeugen eine generische Methode, die das Objekt für die weitere Ausführung liefert. function getXmlHttp() { // Internet Explorer try { xmlHttp = new ActiveXObject(\"Msxml2.XMLHTTP\"); } catch(e) { try { xmlHttp = new ActiveXObject(\"Microsoft.XMLHTTP\"); } catch(e) { xmlHttp = false; } } // Mozilla, Opera und Safari if (!xmlHttp && typeof XMLHttpRequest != 'undefined') { xmlHttp = new XMLHttpRequest(); } return xmlHttp; } Unserem gerade erzeugten Request-Objekt geben wir die URL und die Parameter zum Abruf der Details. Auch hier bietet es sich an, gleich zu Beginn eine generische Routine zu schreiben, welche in beliebigen Kontexten genutzt werden kann. function getXmlResponse(cMethod, cParameter) { var xmlHttp = getXmlHttp(); if (xmlHttp) { xmlHttp.open('GET', 'ajax.afp?action=' + cMethod + '&parameter=' + cParameter, true); xmlHttp.onreadystatechange = function () { if (xmlHttp.readyState == 4) { var tooltipcaption = findDOM(\"tooltipcaption\"); var tooltiptext = findDOM(\"tooltiptext\"); if(tooltipcaption) { tooltipcaption.innerHTML = cParameter; tooltiptext.innerHTML = xmlHttp.responseText; settip('tooltip'); } } }; xmlHttp.send(null); } } Abschliessend verarbeiten wir die Antwort des Webservers und verteilen die einzelnen Informationen auf die sichtbaren HTML-Controls. Damit haben wir die visuelle Darstellung der Kundendaten durch den Wechsel des Kunden in der Auswahlliste vollständig realisiert, ohne dass dazu die Seite neu aufgebaut werden musste. Durch die asynchrone Natur unseres Request/Response-Paares kommt die Aktualisierung leicht verzögert. Ein Umstand den man aber sicherlich gerne im Vergleich zum Komplettaufbau in Kauf nimmt, und einem Auftraggeber erläutern kann. Das war's für den Einstieg in die Welt der asynchronen Kommunikation mit JavaScript und XML. Und bitte lasst den Haushaltsreiniger unter der Spüle stehen. Der könnte eurem Rechner lediglich schaden denn nutzen. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ajax-der-schnelle-kchenreiniger-fr-die-website.html'; this.page.identifier = '9271ed1c97_idajax-der-schnelle-kchenreiniger-fr-die-website'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "aktuelles-umfrageergebnis-und-weiterer-ansatz.html": {
    "href": "aktuelles-umfrageergebnis-und-weiterer-ansatz.html",
    "title": "Aktuelles Umfrageergebnis und weiterer Ansatz - Get Blogged by JoKi",
    "summary": "Aktuelles Umfrageergebnis und weiterer Ansatz Copy Markdown View Markdown GitHub Markdown Source Aktuelles Umfrageergebnis und weiterer Ansatz Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aktuelles-umfrageergebnis-und-weiterer-ansatz'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aktuelles-umfrageergebnis-und-weiterer-ansatz'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Aktuelles Umfrageergebnis und weiterer Ansatz') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-02 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hallo zusammen, ich bin begeistert von der Anzahl der Rückmeldungen in der kurzen Zeit. Danke für euer Mitwirken am VFP Stammtisch - ihr macht Community! Der VFP Stammtisch Speyer hat derzeit 33 Members gelistet, davon habe ich 14 Antworten erhalten. Die Verteilung sieht dabei wie folgt aus: Thema: Cryptographie 10 Messaging 4 Häufigkeit des Stammtisch: Monatlich 11 14-tägig 1 beides okay 2 Gut, daraus lässt sich leicht schliessen, dass wir im Monatsrhythmus bleiben. Die Themenvielfalt wird daher sicherlich nicht weniger. Glücklicherweise kamen Anregungen von euch, dass man auch zwei Themen an einem Abend realisieren könnte... Well, die Idee gefällt mir. Zumal das letzte Treffen ganz klar gezeigt hat, dass man nicht immer zwei volle Stunden benötigt. \uD83D\uDE09 Ich hab' den Termin im Mai auf der Website dahingehend geändert, dass mal beide Themen vorgesehen sind. Cryptographie als Highlight (ca. 1h) und das Messaging als Bonbon (ca. 30-45 Min) dazu. Ich hoffe, dass dieser Ansatz in eurem Sinne ist. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'aktuelles-umfrageergebnis-und-weiterer-ansatz.html'; this.page.identifier = '9271ed1c97_idaktuelles-umfrageergebnis-und-weiterer-ansatz'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-acr.html": {
    "href": "alc4-acr.html",
    "title": "Working with Azure Container Registry - Get Blogged by JoKi",
    "summary": "Working with Azure Container Registry Copy Markdown View Markdown GitHub Markdown Source Working with Azure Container Registry Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-acr'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-acr'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Working with Azure Container Registry') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-01-15 - Filed under DevelopmentAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After completing a challenge to create a React application and experimenting with different ways on how to deploy and operate it on the Google Cloud Platform (GCP) I was wondering about how this would possibly work on Microsoft Azure. The following article describes how to take the same React application as discussed in Create React App (ALC 4.0 Cloud Challenge I) and to deploy it as an App Service on Microsoft Azure using a Docker image. You are going to build a new container and push it to the Azure Container Registry (ACR). First, if applicable, log into your Azure account. > az login To keep the Azure subscription nice and tidy you should create a new resource group for all resources created as described below. Run the following command to create a new group called alc4 in the location of South Africa. > az group create -l southafricanorth -n alc4 You might choose a location closer to you or even use an existing resource group at all. The choice is yours. Create the Azure Container Registry Now, you create an Azure Container Registry (ACR) to push your Docker images to. A registry of Docker and Open Container Initiative (OCI) images, with support for all OCI artifacts. The following command creates an ACR called alc4program and associates it with the newly created resource group from the previous step. > az acr create --name alc4program --resource-group alc4 --sku standard --admin-enabled true Note: The registry name must be unique within Azure, and contain 5-50 alphanumeric characters. Perhaps you should check the features and differences of the various Azure Container Registry SKUs and adjust the above command accordingly. Also, for pricing information on each of the Azure Container Registry SKUs, see Container Registry pricing. You find a lot more information about the Azure Container Registry in the official documentation. Build an image (of React app) With the container registry in place you run the az acr build command to build an image of the existing React application. After successful completion the image is pushed to the registry. > az acr build --file .\\Dockerfile --registry alc4program --image alc4azure . Note: The . at the end of the command is essential and sets the location to the current directory. Building the image for Azure Container Registry is somehow identical to Working with Cloud Build on the Google Cloud Platform. One command that does all the hard work for you. It takes a few minutes to complete the build and push it to the ACR. Maybe time for a little tea break. Deploy to web app Navigate to your Container registry on Azure and choose Services > Repositories to access the newly created Docker image. Select the image to open the details blade. In the extended dropdown menu choose the entry Deploy to web app to create a new app service using the container of the React application, as shown below. This opens the blade Web App for Containers and you specify a few attributes, like site name, resource group, location and operating system, etc., about how to deploy the container as an app service in Azure. Click on the Create button and wait until the web app has been commissioned. A notification is going to inform you as soon as the web app has been fully deployed and accessible via the internet. Open the App Service resource and check the Overview information. The Status field indicates the current state of your web application. Click on the link provided in the URL field to open a new browser tab and visit your recently created web application. The Azure App Service is comparable to the features of the Google Cloud App Engine environments. Whether it's Standard or Flex would mainly depend on your choice of runtime. However, given above described simplicity of deploying the container image as a web application it feels a bit more like Using Google Cloud Run compared to App Engine. Azure App Service has HTTPS built-in Just like Cloud Run, each Azure App Service gets an out-of-the-box stable HTTPS endpoint, with TLS termination handled for you. Using your own domain You might have seen that the auto-generated URL App Service allocates to your service isn't the most user-friendly. Conveniently, App Service offers custom domains out-of-the-box. Image credit: Xavier Coiffic if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-acr.html'; this.page.identifier = '9271ed1c97_idalc4-acr'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-build.html": {
    "href": "alc4-cloud-build.html",
    "title": "Working with Cloud Build - Get Blogged by JoKi",
    "summary": "Working with Cloud Build Copy Markdown View Markdown GitHub Markdown Source Working with Cloud Build Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-build'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-build'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Working with Cloud Build') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-11-23 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); This article has been inspired by a number of conversations in the ALC 4.0 program about the problems some scholars were facing while trying to build the Docker image of the React app on their local machine. Particularly learners that are still running a machine on either Windows 7, Windows 8.x or the Home edition of Windows 10 seem to have trouble using Docker. The latest Docker Desktop requires to run on Windows 10 Professional, if I'm not completely mistaken. Older versions of Windows would need Docker Toolbox which isn't maintained well anymore. Some suggested to use Google Cloud Shell for development, for building and deployment of the image to a container registry. Which works nicely. However it requires to be online. This might incur unwanted cost based on bandwidth and data transfer volume. Using Google Cloud Build seems to be a perfect fit to reduce the cost of being online and being independent of own hardware constraints. Google Cloud Build Cloud Build is a service on the Google Cloud Platform that offers you to continuously build, test, and deploy. And best of all, it also works on-demand. Here's the official description. Cloud Build, Google Cloud’s continuous integration (CI) and continuous delivery (CD) platform, lets you build software quickly across all languages. Get complete control over defining custom workflows for building, testing, and deploying across multiple environments such as VMs, serverless, Kubernetes, or Firebase. Get more information about Cloud Build on their website. Submit to Cloud Build All steps to prepare the local development environment to create a Docker image are identical to the instructions described in Working with Docker (ALC 4.0 Cloud Challenge I). But instead of building the docker image locally or in Cloud Shell you would submit your work to Cloud Build with the following command. > gcloud builds submit -t gcr.io/alc-4-program/alc4cloud:cloudbuild . That's it... The gcloud builds submit command looks a bit like a combination of docker build and docker tag as used in the referred blog article: > docker build -t alc4cloud . > docker tag alc4cloud gcr.io/alc-4-program/alc4cloud The command syntax is thoroughly described in the reference section on gcloud builds submit. As soon as you submit your work to Cloud Build you should see the following output, of course project ID and build IDs will be different. The submission process is dependent on the complexity of your Dockerfile build instructions and your mileage may vary. The image build of the React app takes approximately one minute. At the end you should see a few lines ending in Pushed and a summary of the Docker image being uploaded to Container Registry. Congrats! You successfully submitted the React project. Navigate to Container Registry > Images in the Cloud Console and have a look at the details of the image. You should a similar overview like below. Now, your docker image is ready to use in Kubernetes or App Engine Flex environment. Continue the journey and deploy to Google Kubernetes Engine. Don't forget to check out the Cloud Build documentation for more how-to guides and concepts. Enable Cloud Build API Take note that the Cloud Build API is not enabled by default in your GCP project. If you get the following response on execution of the gcloud builds submit command the API is not enabled. Confirm the question with Y and wait as instructed to retry the submission. Otherwise, you can enable the API yourself in Cloud Console as described below. However, before you enable the API for Cloud Build make sure that billing is enabled for your Google Cloud Platform project. Navigate to APIs & Service > Library in the sidebar menu. Then search for Cloud Build and review the results shown. Chose Cloud Build API and then enable the API in the following screen. After a short while of waiting you should see the following status. Now, retry the submission and watch the docker image being generated and pushed to Container Registry. Pricing of Cloud Build Google Cloud Platform provides a generous free tier to work with Cloud Build. Say goodbye to managing your own build servers with 120 free build-minutes per day and up to 10 concurrent builds included. Working on a small project like the React app it seems to be a no-brainer to use Google Cloud Build to outsource the image building process completely. Let the machines do the hard work and save yourself CPU time on your computer. Let me know in the comments below how smart you are using Cloud Build, and maybe share some build recipes with other readers. Image credit: Chitto Cancio if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-build.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-build'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-challenge.html": {
    "href": "alc4-cloud-challenge.html",
    "title": "ALC 4.0 Cloud Challenge I - Get Blogged by JoKi",
    "summary": "ALC 4.0 Cloud Challenge I Copy Markdown View Markdown GitHub Markdown Source ALC 4.0 Cloud Challenge I Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-challenge'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-challenge'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ALC 4.0 Cloud Challenge I') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-10-18 - Filed under DevelopmentActivityAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Today I finally found a few chunks of time to run through the challenge. It was an interesting journey with some minor hiccups. I documented all steps as a thread on Twitter, starting in the morning: Working on the current Google Cloud Challenge I#100DaysOfExam#150DaysOfALC4 #GoogleAfricaDeveloperScholarship ✔ Create repo on GitHub ✔ Create app ✔ Init local git repo ✔ Add GitHub remote ✔ Push to master \uD83D\uDC31‍\uD83C\uDFCD Code... cc: @andela_alc — Jochen Kirstätter (JoKi) (@JKirstaetter) October 18, 2019 Consider the Twitter thread as a high-level roadmap that came together during the day while actually doing my daily chores in regards to contracted software development. The challenge was done in several chunks, almost identical to the content tweets. The whole resulting implementation is publicly available on GitHub here: https://github.com/jochenkirstaetter/andela-cloud-challenge And the final result can be seen here: https://alc4cloud.kirstaetter.name/ I'll see that I'll be able to document each of those steps in a blog article. With proper explanation, commands executed, errors observed and resolved, and a couple of extras I added to it. Have a look at the log/timeline in the GitHub repository... Break-down of the challenge Given the amount of information and instructional steps the actual implementation is split into multiple articles. Create React App Working with Docker Google Kubernetes Engine (GKE) Considerations for production readiness Each article can be read independently with minor references between each other. Bonus content With the actual challenge completed I ventured into a few other options available on the Google Cloud Platform and wrote about it. This has been inspired by posts of other ALC 4.0 learners. Working with Cloud Build Using Cloud Run instead of GKE Those articles describe alternative ways to build and deploy the React app on Google Cloud Platform. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-challenge.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-challenge'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-docker.html": {
    "href": "alc4-cloud-docker.html",
    "title": "Working with Docker (ALC 4.0 Cloud Challenge I) - Get Blogged by JoKi",
    "summary": "Working with Docker (ALC 4.0 Cloud Challenge I) Copy Markdown View Markdown GitHub Markdown Source Working with Docker (ALC 4.0 Cloud Challenge I) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-docker'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-docker'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Working with Docker (ALC 4.0 Cloud Challenge I)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-10-24 - Filed under DevelopmentAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Containerising and pushing a previously created React application is the second part of the ALC 4.0 Cloud Challenge I. The following instructions are agnostic to the referred web application however probably you might like to have a read about how to Create an React App, if you're not familiar with the React app. Pre-requisites #1 - Docker Depending on your hardware and operating system you need to have virtualisation enabled and a recent version of Docker installed. Docker provides a way to run applications securely isolated in a container, packaged with all its dependencies and libraries. Windows I'm using Windows 10 Professional and therefore Docker Desktop for Windows but the instructions shall work on Linux and macOS the same way. You can check for the installed version of Docker in PowerShell or command prompt like this: > docker -v Docker version 19.03.4, build 9013bf5 Most probably your version information might be different. Full installation guide on Windows can be found in the official Docker Documentation: Install Docker Desktop on Windows. Linux The Docker extension for Visual Studio Code has a nice piece of advice: On Linux, you must also follow the steps in “Manage Docker as a non-root user” from Post-installation steps for Linux because VS Code runs as a non-root user. There are other interesting sections in the above linked URL. Surely worth to visit the page and make some adjustments to your Linux system running Docker. Create Dockerfile Now, back to Visual Studio Code and the existing React app. In the root folder of the project you create a new file called Dockerfile. Add the following content to it: # build stage FROM node:lts-alpine as build-stage WORKDIR /app COPY package*.json yarn.lock ./ RUN npm install COPY . ./ RUN npm run build # production stage FROM nginx:stable-alpine as production-stage COPY --from=build-stage /app/build /usr/share/nginx/html EXPOSE 80 CMD [\"nginx\", \"-g\", \"daemon off;\"] This Dockerfile defines a multi-stage build which uses two individual images, we call them stages here. The first stage, called build-stage is exclusively used to create a fresh environment from scratch for the React application whereas the production-stage defines the actual Docker image we are going to use later in Kubernetes. The build-stage The build stage pulls an existing image from docker hub that is specialised for the execution of Node.js code. Here, a long-term support (LTS) version of Alpine Linux with minimal Linux environment is used. The build stage is divided into two steps - installation of dependencies and the actual project build. This is similar to the process of creating the React app and then generating a production version. First, we copy the package.json and the yarn.lock from our React project folder to the file system inside the Docker image. Then we resolve all necessary dependencies for the React app by calling the npm install command. Second, we transfer all remaining files from the React project folder into the image and then trigger the production build of our React app by calling npm run build to complete the build stage. The production-stage For this stage we choose another docker image with built-in web server nginx to deliver our React app. Because all generated artefacts from the npm run build are found in the build-stage we are now copying the content of the newly created folder build into this image. The target folder /usr/share/nginx/html is the default location specified in the nginx web configuration. We open port 80 (HTTP) on the Docker image for external access. Maybe think of it like a minimal firewall. And finally, we instruct the production stage to execute the nginx web server and listen to incoming connections on the exposed port 80. Consult the Docker Documentation for more information about the syntax and features of Dockerfile. Use of .dockerignore To make your build context as small as possible add a .dockerignore file to your root directory of the React folder and copy the following into it. This helps to avoid unnecessarily sending large or sensitive files and directories to the Docker daemon and potentially adding them to images. .git node_modules build Obviously, we do not want to have all information about the git repository inside the image. And as explained in the previous paragraph the React app is always build from scratch. Hence, we can exclude all node modules that npm install takes care of, and the build folder is newly generated on each run of the npm run build statement. Refer to the official documentation to learn more about .dockerignore. ‍Build docker image Now, the setup is ready to actually build a Docker image of the React app. Execute the following command in the root directory of the project. > docker build -t alc4cloud . This might take a while depending on your machine. Maybe time to have a break. The switch -t gives the image a name and optionally a tag, using the format name:tag. In our case we specify a name only. In absence of a tag value Docker automatically uses latest as tag. Other command switches and more details are described in the Docker reference documentation: Build an image from a Dockerfile. Minor troubleshooting on Docker for Windows... A word of caution. I was not able to complete the build-stage due to failed execution of the RUN statements, particularly react-scripts couldn't be found. In case you run into same trouble you need to install react-scripts globally to resolve this issue. Modify the Dockerfile by adding the installation into the build-stage right after you pulled the image from the repository. # build stage FROM node:lts-alpine as build-stage RUN npm i -g react-scripts WORKDIR /app COPY package*.json yarn.lock ./ RUN npm install COPY . ./ RUN npm run build This worked for me. The production-stage remains unchanged. Run docker image After successful execution of docker build you should spin up the generated image locally to verify that it works as expected. > docker run -p 8080:80 --name alc4_localtest alc4cloud This is going to run the generated production-stage with the name:tag that you specified in the build command earlier, here alc4cloud. The additional switches allow you map the exposed port 80 of the docker image to an unprivileged port, here 8080 on your host machine. Meaning, that you can open your browser and navigate to the following URL. http://localhost:8080 The command option --name assigns a name to your container instead of getting a randomly generated one. See reference entry Run a command in a new container in the Docker documentation for more details. Other practical Docker commands To see a list of available images on your machine or any repository (docker images). > docker images List the containers running on your host system (docker ps). > docker ps You can add the option -a to see all containers, not just running ones. And to stop one or more running containers (docker stop). > docker stop alc4_localtest If you are happy with the outcome of the container it is time to push the React image to an online repository. Pre-requisites #2 - Google Cloud Platform Install the Google Cloud SDK on your machine to be able to interact with Google Cloud Platform through the command-line interface (CLI). The Cloud SDK is the professional way to use Google Cloud Platform (GCP) and it is essential to run repetitive and automated tasks. For the following steps log into the Google Cloud Console. Enable billing If not already done, make sure that Billing is enabled in your account. This is needed to use certain services like Google Kubernetes Engine. Create a project Optionally, you can create a designated project in your GCP account used for the containerised deployment of the React app. It is good practice to keep resources isolated from each other instead of having to deal with a little pool of chaos. Enable Google Container Registry API The API for the Container Registry is not enabled by default. In the main menu choose APIs & Services > Library and search for Container. On the next page make sure that the right GCP project is active and click on Enable. With the Google Container Registry API enabled you can now use the Docker CLI client to manage your images on GCP. Connect to Google Cloud Platform The ALC 4.0 Cloud Challenge I specification mentions to use Docker Hub. First, I do not have an account on Docker Hub and I'm (currently) not interested to create one either. And second, this whole study track is all about Google Cloud Platform. So, let's use the Google Container Registry (GCR) instead. Either you open the built-in terminal in Visual Studio Code or you use a terminal / command prompt for the next commands. To use the command-line interface for Google Cloud Platform products and services you have to authenticate yourself first. > gcloud auth login This opens your browser and you should log into your Google Cloud account. Confirm the consent dialog to grant certain permissions to the Google Cloud SDK and click on Allow. Review the currently active project and maybe change project context, if needed. > gcloud config set project alc-4-program Also keep an eye for any other information. Above you can see that updates for some Cloud SDK components are available and how to update them locally. Next, we need to configure access to the Google Container Registry. There are multiple Authentication methods available. Following are two options described. For the remaining instructions in this article we are going to use the standalone Docker credential helper. gcloud as a Docker credential helper To authenticate to Container Registry, use gcloud as a Docker credential helper. To do so, run the following command: > gcloud auth configure-docker You need to run this command once to authenticate to Container Registry. Standalone Docker credential helper Docker needs access to Container Registry to push and pull images. You can use the standalone Docker credential helper tool, docker-credential-gcr, to configure your Container Registry credentials for use with Docker. The credential helper fetches your Container Registry credentials—either automatically, or from a location specified using its --token-sourceflag—then writes them to Docker's configuration file. This way, you can use Docker's command-line tool, docker, to interact directly with Container Registry. Check which gcloud components are already installed on your machine. > gcloud components list If the Docker credential helper is not installed already, run the following command. > gcloud components install docker-credential-gcr Then, configure Docker to use your Container Registry credentials when interacting with Container Registry (you are only required to do this once): > docker-credential-gcr configure-docker This is going to add relevant entries of Google Container Registry to your local Docker configuration file, located at %UserProfile%\\.docker\\config.json. After successful completion your JSON file should have the credentials helper and several entries of gcr.io domains. Great! Your machine is ready to rumble the container registry. Push image to Container Registry Now it's finally time to upload the Docker image to Container Registry. First, we create a tag with the following pattern that refers to the local source image: [HOSTNAME]/[PROJECT-ID]/[IMAGE][:[TAG]] with > docker tag alc4cloud gcr.io/alc-4-program/alc4cloud Container Registry expects those values. [HOSTNAME] as listed under Location in the Cloud console. Available options are gcr.io, us.gcr.io, eu.gcr.io, or asia.gcr.io. [PROJECT-ID] is your Google Cloud Platform Console project ID. [IMAGE] is the image's name in Container Registry. [TAG] is optional. Latest is used as default. The resulting list of images should look like this. More information on how to create a tag can be found in the Docker reference. You can also use the ID to tag a local image. Then share your image to Container Registry. > docker push gcr.io/alc-4-program/alc4cloud In the Cloud Console go to Container Registry > Images to verify your image has been uploaded correctly. Note: Images stored in Container Registry can be deployed to the App Engine flexible environment. Using another container registry The ALC 4.0 Cloud Challenge specified to share the image to Docker Hub originally. However, you can use any other repository provider available in Google Kubernetes Engine. The choice is yours. Docker Hub If you prefer to use Docker Hub you would tag your local image using a different pattern: [USERNAME]/[IMAGE][:[TAG]]. > docker tag alc4cloud u12345678/alc4cloud Log into Docker from the console and then push the tagged image. > docker login > docker push u12345678/alc4cloud GitHub Fellow ALC 4.0 scholar George Udosen wrote a nice piece about how to share an image to GitHub and then using it in Kubernetes Engine. Splendid! Our React app has been containerised using Docker and published to Container Registry. In the third part of this series we are going to deploy it to Google Kubernetes Engine (GKE). if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-docker.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-docker'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-k8s.html": {
    "href": "alc4-cloud-k8s.html",
    "title": "Google Kubernetes Engine (ALC 4.0 Cloud Challenge I) - Get Blogged by JoKi",
    "summary": "Google Kubernetes Engine (ALC 4.0 Cloud Challenge I) Copy Markdown View Markdown GitHub Markdown Source Google Kubernetes Engine (ALC 4.0 Cloud Challenge I) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-k8s'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-k8s'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Google Kubernetes Engine (ALC 4.0 Cloud Challenge I)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-11-09 - Filed under DevelopmentAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); During the third part of the series we are covering the steps needed to deploy the previously created Docker image in Google Kubernetes Engine (GKE). Whether you are going to use Cloud Console or Cloud Shell with gcloud command is totally up to your personal preference. I'm trying to cover both ways in the following paragraphs. Quickstart For the real impatient among you, here are the essential commands to get started with the creation of a Kubernetes cluster and the deployment of the React app. There might be a few output warnings which are informative and can be ignored. It takes roughly a minute or so until the external IP address of the service is available publicly. $ gcloud config set project alc-4-program $ gcloud config set compute/zone us-central1-a $ gcloud container clusters create alc4challenge-cluster $ gcloud container clusters get-credentials alc4challenge-cluster $ kubectl create deployment alc4challenge \\ --image=gcr.io/alc-4-program/alc4cloud:latest $ kubectl expose deployment alc4challenge \\ --type LoadBalancer \\ --port 80 \\ --target-port 80 You need to adjust values to your project-id, zone, cluster name, and deployment name. More details on each step in the following paragraphs. Prepare Kubernetes deployment First, let's quickly check that we have the image to deploy available in the container registry. On the GCP you would check the Container Registry for an entry. In case that you're using an external registry make sure that access to your image is granted. Otherwise, you might not be able to pull it into your cluster. Create Kubernetes cluster Although it might an obvious choice to opt-in for the Create cluster option in Kubernetes Engine for this tutorial it is recommended to keep things easy and click on Deploy container. You might skip to the next paragraph then. The amount of options and detailed control over the cluster might be overwhelming and distracting at this stage. However, the brave reader might go ahead and create the cluster using an existing template now... In case you prefer Cloud Shell, you could run the following command and options to create your cluster. $ gcloud container clusters create \"alc4-cluster\" \\ --project \"alc-4-program\" \\ --zone \"us-central1-a\" \\ --num-nodes \"3\" \\ --machine-type \"n1-standard-1\" \\ --disk-size \"10\" \\ --disk-type \"pd-standard\" \\ --enable-autoscaling --min-nodes \"1\" --max-nodes \"5\" \\ --enable-autorepair This should generate a similar output to this. You can also query your cluster with the following command gcloud container clusters list. Creating cluster alc4-cluster in us-central1-a... Cluster is being health-checked (master is healthy)...done. Created [https://container.googleapis.com/v1/projects/alc-4-program/zones/us-central1-a/clusters/alc4-cluster]. To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/us-central1-a/alc4-cluster?project=alc-4-program kubeconfig entry generated for alc4-cluster. NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS alc4-cluster us-central1-a 1.13.11-gke.9 34.66.143.71 n1-standard-1 1.13.11-gke.9 3 RUNNING All details and other configurations are explained in Create a cluster in the documentation of Kubernetes Engine. Keep in mind that those computing resources are going to cost you money. You might consider to start with a single node only instead of the default value of three nodes. Create Kubernetes workload If you choose to Deploy container or you navigate to Workloads and click on Deploy you will be guided by a deployment wizard through every single step. Keep the pre-selected option of Existing container image and click on Select to navigate to your Docker image in the Container Registry. When you use an image that is located in an external registry like on Docker you would specify the value of Image path with the tag you used previously, like we used u12345678/alc4cloud to tag the image on Docker. Optionally, you could define environment variables or add another container to the cluster. For this deployment we can ignore those options and hit Continue. Deploy cluster Next, you enter a few values for the configuration of your deployment. Give it a unique application name and perhaps a label in the process. You should be sure to set the Zone to us-central1-a. The free tier of using Google Cloud Platform gives you certain quota for resources for free per month. Would be positive to take advantage of this perk. If you want to keep track of your configuration for other use then you might like to keep a copy of the YAML declaration. --- apiVersion: \"apps/v1\" kind: \"Deployment\" metadata: name: \"alc4challenge\" namespace: \"default\" labels: app: \"alc4challenge\" spec: replicas: 3 selector: matchLabels: app: \"alc4challenge\" template: metadata: labels: app: \"alc4challenge\" spec: containers: - name: \"alc4cloud-sha256\" image: \"gcr.io/alc-4-program/alc4cloud@sha256:c9bd91e7e1af2babbfd6e19d01e158c58fe1ceabda7ece59dc9bb8b63a436445\" --- apiVersion: \"autoscaling/v2beta1\" kind: \"HorizontalPodAutoscaler\" metadata: name: \"alc4challenge-hpa\" namespace: \"default\" labels: app: \"alc4challenge\" spec: scaleTargetRef: kind: \"Deployment\" name: \"alc4challenge\" apiVersion: \"apps/v1\" minReplicas: 1 maxReplicas: 5 metrics: - type: \"Resource\" resource: name: \"cpu\" targetAverageUtilization: 80 You might save this as deployment.yaml file. And deploy it in Cloud Shell with the following command. $ kubectl apply -f deployment.yaml Finally, hit the Deploy button and watch the progress of deployment. Maybe it's time for another cup of tea or a light snack to stretch your legs. The deployment of the cluster is going to take a few minutes at least. In case that you created the cluster using Cloud Console or on a different machine you might not have the right kubeconfig. We need to fetch information about cluster endpoint and authentication to continue using Cloud Shell. $ gcloud container clusters get-credentials alc4challenge-cluster --zone us-central1-a --project alc-4-program Fetching cluster endpoint and auth data. kubeconfig entry generated for alc4challenge-cluster. The command creates the .kube folder and allows us to continue working with kubectl command. Should you get a connection refused response on your commands like below you forget to retrieve the cluster endpoint. $ kubectl get pods The connection to the server localhost:8080 was refused - did you specify the right host or port? Afterwards, you can query and configure your cluster using kubectl like so. $ kubectl get pods NAME READY STATUS RESTARTS AGE alc4challenge-79bbd4c9f9-9km4n 1/1 Running 0 7m36s You can read more about Configuring cluster access for kubectl in the official documentation. Create load balancer After completion of deployment you are going to be greeted by the Deployment details. Verify that all details are as expected. At the top you'll see a notification bar suggesting to create a service to Expose your cluster in order to let others access it. Our React app is exposed on TCP port 80 by default and we use that same port on the load balancer. No further changes needed in the above configuration. Click on Expose and wait until the service has been deployed completely. The YAML declaration can be stored as service.yaml file for further reference. --- apiVersion: \"v1\" kind: \"Service\" metadata: name: \"alc4challenge-service\" namespace: \"default\" labels: app: \"alc4challenge\" spec: ports: - protocol: \"TCP\" port: 80 selector: app: \"alc4challenge\" type: \"LoadBalancer\" loadBalancerIP: \"\" Like previously you deploy the load balancer service in Cloud Shell like so. $ kubectl apply -f service.yaml Give it a minute or so to complete. Get public IP address Approximately one minute later you should see the Service details like shown below. Under External endpoints you can see the external IP address that has been assigned to the load balancer service. If you prefer to work with kubectl you can query the status of your services like this. $ kubectl get services NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE alc4challenge-service LoadBalancer 10.0.10.53 34.70.0.91 80:32094/TCP 10m kubernetes ClusterIP 10.0.0.1 <none> 443/TCP 12m Here it is 34.70.0.91. Let's have a look at our deployed React app running as Docker image in a cluster on a Google Kubernetes Engine. Congratulations! The Cloud Challenge has been completed successfully. In the fourth and last part of this series I'm adding a few aspects to think about when deploying a web application into production environment. Find out more here: Considerations for production readiness (ALC 4.0 Cloud Challenge I). if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-k8s.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-k8s'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-meeting.html": {
    "href": "alc4-cloud-meeting.html",
    "title": "ALC 4.0 Cloud - Phase II Online Meet up - Get Blogged by JoKi",
    "summary": "ALC 4.0 Cloud - Phase II Online Meet up Copy Markdown View Markdown GitHub Markdown Source ALC 4.0 Cloud - Phase II Online Meet up Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-meeting'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-meeting'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ALC 4.0 Cloud - Phase II Online Meet up') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-09-02 - Filed under CommunityAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); With Phase 2 being kicked off a few days ago, there have been some changes communicated by the program admins of the Andela Learning Community (ALC). One of those improvements is that there are going to be regular meetings - offline and online - to assist all learners in their endeavour. In preparation of the first online meetup aka. Google Africa Developer Scholarship Online Meetup, ALC admins contacted me to run a session being a Learning Community Ambassador (LCA). Great stuff I thought given that I'm in the 4.0 program as an LCA for mobile web development. However, to my astounishment, I was elected as LCA for the Cloud track. Uhm... Not sure what has happened there because as I wrote in my other blog Share your journey - #150DaysOfALC4 I only started with the Google Cloud Platform (GCP) thanks to this program. Meaning, I'm definitely not an expert in GCP, not even remote. Nonetheless I agreed to speak for approximately an hour given that I had a few aspects and tips on my mind that I wanted to share with my fellow study buddies. Non-Andelan Location Study Group At the same time there had been several requests from engaged learners and LCAs in various non-Andelan countries / locations whether it would be possible to host an offline meeting on own merits. Given the schedule for phase 2 it seems to be a great opportunity to bring learners from all three tracks together. I volunteered to provide premises, internet connectivity and light catering for learners in Mauritius at my business office in Quatre Bornes. And thanks to the help and effort by the program managers about ten learners placed their RSVP to come and join me on the 31st August. As usual on a Saturday, I have my children with me, and they gave me a helping hand to arrange the venue a little in preparation for our guests. Despite the confirmed RSVPs two learners from the Android track actually came. We had good conversations about the ALC 4.0 program in general, the challenges during phase 1, and about the targets they want to achieve. Most interestingly, both are from abroad and they are currently studying for their masters at a local university. Google Africa Developer Scholarship Online Meetup The projected time came and the meeting started as scheduled on Zoom plus additional live streaming on Youtube. With the web track starting we used the first hour to talk about our expectations for phase 2, and how tough it might get to stay in the program for the upcoming project period. Remember, the current number of 11,000 participants is going to be reduced to roughly 4,000 only. Architecting on the Google Cloud Platform Of course, we kept an eye on the web session of Joseph irrespective our own program tracks. He gave some nice demos and thankfully stayed within the one hour slot allocated to him. Next, it was my turn to explore the rich topic of Architecting on the Google Cloud Platform. As the topic itself is quite broad and rich on content I tried to bring out the essential parts that would be most relevant for Cloud learners in the current ALC 4.0 program and how to probably add a few more resources to the available ones. After a quick introduction of myself, as partly described in ALC 4.0 Cloud - Slack questions - I went through the first batch of questions that have been posted on Sli.do ahead of the meeting. The full list of questions can be found here: Phase II Global Meetup 1.0 The interesting questions were about whether one should sign up for the Google Cloud Free Trial or create an account on Qwiklabs. If you are considering to be a future professional in cloud computing on the Google Cloud Platform... then absolutely yes, you should do so. To keep the cost for those accounts low watch out for any kind of promotions or competitions online that might give you credits on those platforms to use instead of spending your well-earned money. All the links... In regards to the learning paths available to us on Pluralsight have a look on their official help page about What courses do I have access to with the ALC 4.0 program? Additionally, keep an eye on the Pluralsight author Google Cloud. Best might be to follow in order to receive notifications when new material has been published. Next, I reviewed some new questions like this one: Qwiklabs links are limited to very specific tasks, and any misuse will lead to a ban, Moreover the labs provide limited time. A good approach would be to open a personal account and use the trial, but the problem is personally I don't have a visa to register for the free account, and therefore unable to do most of the labs in Pluralsight. I really want to use a free account, as this will provide an easier method to practice. The question is how can I achieve this? Yes, that is correct. Already during phase 1 I came across another initiative by Google that would be of help in accessing more Qwiklabs for free. Until the end of September there is a 12-week certification challenge going on to get Google Cloud Certified. Despite the limitation to 25 countries to receive an exam voucher one can still sign up, and receive 30 days sponsored access to Qwiklabs. Then I shifted the focus to the official documentation of the Google Cloud certifications over at https://cloud.google.com/certification/ which not only gives you an overview of the available certifications but provides all the details about what topics are going to be covered during the exam. The outcome of the ALC 4.0 program is to register for the Associate Cloud Engineer (ACE). To be well prepared one should go through the details of the Exam guide and familiarize yourself with the format by using the Practice exam. After the associate level of Google Cloud certifications one might like to go further and confirm knowledge at the Professional level, like the Professional Cloud Architect, the Professional Data Engineer or the Professional Cloud Developer. But that's beyond the scope of the ALC 4.0 program. For that, one might like to look into the course objectives of Architecting with Google Compute Engine on Qwiklabs for orientation. Additionally, there is a Challenge: GCP Architecture with several hands-on-lab modules. In case that you sign up on Qwiklabs you could enroll yourself for this quest. Next, you need to be proficient on the various service options that cloud computing has to offer. These are the various XaaS abbreviations that you come across very often. You need to be sure what Infrastructure-as-a-Service is all about, compared to Platform-as-a-Service, or newer topics like Function-as-a-Service. My recommendation is to read Paul Kerrison's excellent article Pizza as a Service 2.0 as an analogy to the different cloud computing options. In case you prefer to watch more videos compared to reading documentation I suggest that you have a look at the official channels of Google Cloud and explicitly their playlist called Google Cloud Certification. Plus, there is a dedicated channel on Google Cloud Platform. And here in particular you should check out their Google Cloud Platform Essentials playlist. If you are more into the practical exercises I recommend that you have a look the various Codelabs provided by Google Developers. Those hands-on-labs can be filtered by Cloud services so that you get the most out of it. Next, it was about time to actually talk about architectural decisions using Google Cloud Platform. Let's start with the following flow chart regarding the multiple options to store data on GCP. After another sessions of questions, see below, I continued to cover a few more aspects of storage options. Wilson Mar wrote an excellent article about what he calls GCP (Google Compute Platform/Google Certified Professional). That article is an invaluable resource of information. If there is only one take away from this session then it shall be Wilson's article. After storage options we had a closer look into the available Compute options on the Google Cloud Platform. Most probably you might like to read the article Choosing the right compute option in GCP: a decision tree to make the right and informed choice for your computing environment. The article offers another flow chart with a few questions to move you into the right directions. Back to Pluralsight, I showed how to use Notes in order to bookmark certain information in a video course. This gives you a quick and easy access to key aspects of cloud services or features. Or any other type of specific information that you would consider important. A few more questions to consider From the questions during the live session I picked a few on Google Cloud, on the exam preparation for the Associate Cloud Engineer, and on Pluralsight. What's the prospects for those who complete the course and pass the exams - Professional Cloud Architect With a successful pass of either the Associate Cloud Engineer or perhaps the Professional Cloud Architect you confirm your knowledge of the Google Cloud Platform. Those exams are standardized on a global level. Meaning, that you will have to provide the right answers on the same level of difficulty like anyone else on the planet. Those exams do not have regional or cultural difference. By passing one of those exams you are in an equal position to someone from any other country including any of the industrialised countries. There is a whole job market looking for certified professionals. Maybe you like to watch my presentation Switch Conference Talk by Jochen Kirstätter: Confirm your knowledge with a certification on Youtube were I provide more background information on the importance of professional exams and industry certifications. So in order to pass the phase 2 for cloud track we have to go through the 12 weeks challenge on Qwiklab? No, as explained in the recording the 12-week certification challenge is a completely different program offered by Google directly compared to the ALC 4.0 program offered by Andela with partners. It is an additional resource to get free of charge access to more Qwiklabs. You get access to all Qwiklabs for a period of 30 days at no extra cost. Google cloud certificate if valid for how many years? In the following Cloud On Air recording - Why Certify? Everything you need to know about Google Cloud Certification - Anna Baird and Carl Tanner give you an insight about the certification periods. Personally, I appreciate that the Google Cloud certificates have an expiration after two years. In general, technology changes quickly in IT, especially in fields like Cloud Computing. Your knowledge of now might have little to no value in the near future... who knows? Having to renew certifications shows that you are constantly in touch with the current trends and technology available, and that you are not resting in the past. What's your take on old videos PS which doesn't correspond on new GCP? That it clearly confirms the quick development in cloud computing. Take the Pluralsight video courses on Cloud Functions. They have been recorded back in 2017 and 2018 when Functions on GCP were still in beta state. Since then Cloud Functions have reached general availability. And the implementation can be realised in Node.js, Go and Python instead of node.js only. Nonetheless the courses are solid material to prepare yourself for the ACE exam. Where do I find the IQ assessment link for phase 2 on Pluralsight? Log into your Pluralsight account and then navigate to the Skill Assessments page. There you will probably get a list of retakes available and a number of other Skill IQ assessments. In case of doubt you might consider to reach out to the support team of Pluralsight. Perhaps they can assist to take a Skill IQ assessment again. My takeaway from this meeting Doing the session live on Zoom was actually good fun and it worked out nicely with two cameras and the screen sharing option. Maybe I'm going to explore that kind of distribution of information more by the end of the year. Regarding the actual topic of the session, Architecting on GCP, it is important to gain the necessary knowledge in order to make an informed decision about which cloud services and functionalities to choose and use. The above shown flow charts on Compute and Storage options on the Google Cloud Platform already provide a good fundament to start with. Phase 2 of the Cloud track has more than 115 hours of video content to offer on Pluralsight. That's quite a lot on one side but there are also courses that go beyond the actual target on this program: Preparation for the Associate Cloud Engineer exam. To avoid being overwhelmed with the additional material one shall regularly review the official Exam guide for the actual certification. Stay focused on target: Associate Cloud Engineer exam Complete recording on Youtube Thanks for reading this far. In case that you missed the live session on Zoom perhaps you like to watch the recording on Youtube. As per agenda of the Google Africa Developer Scholarship Online Meetup there are three parts. The Cloud content covered in this article starts at the 1:02:22 mark and runs till the 2:03:40 mark. Just a little bit more than the allocated 1 hour. Which I'm very happy about. Last but not least, feel free to write any feedback, comments or questions in the comment section below. And you can reach out to me either on the ALC 4 Phase II Slack workspace or preferable on Twitter: @JKirstaetter Image credit: Google Cloud Platform if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-meeting.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-meeting'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-react.html": {
    "href": "alc4-cloud-react.html",
    "title": "Create React App (ALC 4.0 Cloud Challenge I) - Get Blogged by JoKi",
    "summary": "Create React App (ALC 4.0 Cloud Challenge I) Copy Markdown View Markdown GitHub Markdown Source Create React App (ALC 4.0 Cloud Challenge I) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-react'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-react'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Create React App (ALC 4.0 Cloud Challenge I)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-10-20 - Filed under DevelopmentAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Creating a React application marks the first part of the ALC 4.0 Cloud Challenge I given to scholars. That process is well-described in the Getting Started page of official website of Create React App and I'm going to add a few remarks below only. Pre-requisites The following steps are dependent on the availability of Node.js on your machine. Hence, you should verify that you have a recent version of Node.js installed. You can check this by opening a terminal or command prompt and run the following command: $ node -version v11.15.0 Of course, the returned version information might vary on your system. If you don't have Node.js installed, get the latest version from the Downloads section. Create the app Now, let's come up with a name, like alc4-react, and create the application with the following command executed in either terminal or command prompt: $ npx create-react-app alc4-react The Create React App template engine generates a new React application with the provided name, here alc4-react in a sub-folder of the same name. This will take a few minutes to fetch the necessary node modules and create the application structure. You might grab a cup of coffee/tea until the process has been completed successfully. After completion, change into the new created folder $ cd alc4-react and inspect the project files with your favourite text editor. Mine is Visual Studio Code. $ code . To check whether everything is as expected you should run the following command to launch a local development server: $ npm start Well done! You created and launched a new React app on your local machine. Hit CTRL+c to stop the local development server. Local git repository During the generation process of the React app the template already initialises a git repository locally. Additionally, a .gitignore file is also created to skip certain files and folders to be committed to the repository. In case that you're familiar with git you might like to check and optionally set a few configuration settings. $ git config --list ... $ git config user.name \"Jochen Kirstätter\" Adjust them according to your needs. Create repository online on GitHub As a good practice it is recommended that any local source code should be replicated / backed up to an online resource. Whether this is GitHub, Azure DevOps, GitLab, Bitbucket, etc. is completely your personal choice. Exposing your source code online also enables you to invite other software developers to have a look. This may provide you with suggestions and perhaps pull requests with improvements. In this tutorial we are going to use GitHub. Navigate over to the GitHub web site, log into your account or create a new, free one, and create a new repository for the freshly generated React app. Keep the checkbox Initialize this repository with a README unchecked. We already have this file and it would create a conflict situation. Do not add a .gitignore file for same reason. Optionally, consider to choose a license for your repository. I'm comfortable with the MIT License. You might like to explore other options. Next, hit that green Create repository button to complete this step. Add GitHub remote With the remote repository available on GitHub it is now time to connect it to the local one as origin entry. $ git remote add origin https://github.com/jochenkirstaetter/alc4-react.git You can check the remotes with the following command: $ git remote -v origin https://github.com/jochenkirstaetter/alc4-react.git (fetch) origin https://github.com/jochenkirstaetter/alc4-react.git (push) Multiple remotes are possible given unique names. That's mainly interesting when you fork from another online repository and you would like to keep track of an upstream of changes and to be able to update your forked repository from time to time. Push to master Finally, push your local repository to the new created remote one with the following command: git push -u origin master ‍The output is going to look similar to the following. Congratulations! You added your React app to an online repository successfully. Note: The chosen name for the git repository was for demonstration purpose only. Here, you can find my actual code repository on GitHub. Code... Now, with a safety net for any code adjustments in place it is time to start tweaking the files and source code of the React application. First, I exchanged a few files in the public folder completely. I copied an existing favicon.ico to replace the React one. I also used the Share your Journey image of the ALC 4.0 program to replace logo192.png and logo512.png. Note: The background image shown while running the React app is based on the SVG image logo.svg located under the src folder. I used one of the many \"PNG-to-SVG\" services online to create an SVG version of the ALC 4.0 campaign image. Then I changed the content in the file manifest.json to be more specific about the purpose of this application: { \"short_name\": \"ALC 4.0 Cloud\", \"name\": \"ALC 4.0 Cloud Challenge I\", \"icons\": [ { \"src\": \"favicon.ico\", \"sizes\": \"64x64 32x32 24x24 16x16\", \"type\": \"image/x-icon\" }, { \"src\": \"logo192.png\", \"type\": \"image/png\", \"sizes\": \"192x192\" }, { \"src\": \"logo512.png\", \"type\": \"image/png\", \"sizes\": \"512x512\" } ], \"start_url\": \".\", \"display\": \"standalone\", \"theme_color\": \"#000000\", \"background_color\": \"#F7AB33\" } See new value of short_name, name and background_color attributes. The remaining key/value pairs stayed untouched. Still in the public folder, you might like to review and adjust the <title> tag in the index.html file to your likings. <title>ALC 4.0 Cloud Challenge I</title> When done, commit your changes with a descriptive comment and push to your GitHub repository. Update README.md Here, I simply copied the content of the original Google Docs online resource and converted it to Markdown syntax. This allows me to have a record of the challenge and I don't have to keep a bookmark to Google Doc (which could be pulled at any time). With my own copy I can amend the information to my gusto, and lastly it helps other developers to have a brief overview of what the source code in the repository is actually about. Personalise the React application There are more artefacts in the default React app that you might like to inspect and amend. Open the package.json file in the root folder of the project and give a few more details, like a proper name and description. { \"name\": \"andela-cloud-challenge\", \"version\": \"4.0.0\", \"description\": \"ALC 4.0 Cloud Challenge\", \"repository\": { \"url\": \"https://github.com/jochenkirstaetter/andela-cloud-challenge.git\" }, \"readme\": \"README.md\", \"license\": \"MIT\", \"private\": true, \"dependencies\": { ... } } Maybe you like to add information about your GitHub repository, Read me instructions and which license your application is using. Review UI and CSS Right now, the React app is still in its original state of visual appearance. The choice of fonts, colours, etc. might not be your personal preference. Perhaps... \uD83D\uDE09 All styling is found in the file App.css under the src folder. I didn't like the default background colour and changed it. I also added another CSS class for an additional status indicator of my application (more on that later). Activate service worker With all modifications in place I wanted to make sure that my React app is PWA-ready. PWA stands for Progressive Web Application and provides some cool features like better reliability, working offline, faster loading, background synchronisation, caching, and others. Open the file src/index.js and follow the instructions given at the end. It also enables that your web site can be added to the home screen on mobile devices, and recently on desktop systems. Test local environment While working on all those modifications described above you could have kept the local development server running in the background. The React app is instantly reloaded on saving any change. Let's run the local environment again, in case it's not active, with the following command: $ npm start The expected result looks like this. Yours might be different, of course. See the icon and title of the browser tab among other modifications done. Awesome! This is the first part of a series regarding the completion of the ALC 4.0 Cloud Challenge I. The article describes the creation and customisation of a React app and how to secure all modifications using an online repository on GitHub. In the next article we are going to pack the React app as a Docker image and push it to the Google Container Registry (GCR) for further use. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-react.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-react'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-ready.html": {
    "href": "alc4-cloud-ready.html",
    "title": "Considerations for production readiness (ALC 4.0 Cloud Challenge I) - Get Blogged by JoKi",
    "summary": "Considerations for production readiness (ALC 4.0 Cloud Challenge I) Copy Markdown View Markdown GitHub Markdown Source Considerations for production readiness (ALC 4.0 Cloud Challenge I) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-ready'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-ready'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Considerations for production readiness (ALC 4.0 Cloud Challenge I)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-11-09 - Filed under DevelopmentAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The mandatory steps to finish the challenge are now behind us and we have time for a few thoughts and considerations when working with containerized web applications. Configure DNS to ease access After creating the LoadBalancer service in Google Kubernetes Engine we got an external IP address that let others access the React application over the internet. That's good. However, it is more common to have a domain name in place instead of using an IP address. A domain name would also allow us to decouple the deployed app from having to use a fixed address. Apart from checking out Google Cloud DNS to purchase a new domain it might be more likely that either you or your company already has an existing domain name. This enables us to create a subdomain for the cluster. I'm using Cloudflare to configure DNS records for one of my domains. I created an A record for the public IP address of my cluster, i.e. alc4cloud.kirstaetter.name which is also proxied by Cloudflare. Enable HTTPS only Using Cloudflare comes with SSL certificate free of charge. And it's flipping a switch only. Any request to the HTTP-based URL of the React app will be redirected automatically to the secure HTTPS-based address. No further set up needed. Update README During the process of building the Docker image I stumbled over a small issue with the base image used in the build-stage. Hence, I updated the README.md file accordingly and pushed it to my repository on GitHub. While build the Docker image on Windows the process terminated with an error stating that react-scripts command would not be available to complete the build of the React app. Hence, react-scripts are installed globally during the build-stage. RUN npm i -g react-scripts Clean install of app dependencies Still in the build-stage of the Dockerfile we used an npm command to install all dependencies and libraries needed for the React app. However to be sure to start over with a fresh, clean environment and to avoid changes to the packages.json file it is recommend to Use npm ci instead of npm install. # build stage FROM node:lts-alpine as build-stage RUN npm i -g react-scripts WORKDIR /app COPY package*.json yarn.lock ./ RUN npm ci COPY . ./ RUN npm run build This recommendation applies for any kind of automatic build process, i.e. using a continuous integration/development (CI/CD) platform like Maven, Jenkins or Azure DevOps. Add runtime environment variables Perhaps you already noticed it on the different screenshots of my React application that the status and the linked URL of said status changed, depending on progress or stage - Accepted, In Progress and Completed. I wanted to add a little bit of flexible to the React app, i.e. reading and using environment variables. To achieve this I added a <script> tag into the HEAD section the file index.html to load an external JavaScript file env-config.js. ... <link rel=\"manifest\" href=\"%PUBLIC_URL%/manifest.json\" /> <title>ALC 4.0 Cloud Challenge I</title> <script src=\"%PUBLIC_URL%/env-config.js\"></script> </head> That JavaScript file has the following values which have been dynamically created (more on this below). window._env_ = { SITE_URL: \"#\", SITE_STATUS: \"Accepted\", } Which creates an object named _env_ in the browser window after the application has been loaded. This enables us to extend the core functionality of the React app in the file App.js and add the following literals. ... <h1>ALC 4.0 Cloud Challenge I</h1> <img src={logo} className=\"App-logo\" alt=\"logo\" /> <p> Challenge: <a className=\"App-status\" href={window._env_.SITE_URL} target=\"_blank\" rel=\"noopener noreferrer\" > {window._env_.SITE_STATUS} </a> </p> ... As you can see, this setup allows us to use environmental information inside our application using the window._env_ object in JavaScript. Now, to be able to feed the React app with the current information given by the environment we use a shell script that reads the Node.js .env file and generates the content of the referred env-config.js file for the app. #!/bin/bash # Recreate config file rm -rf ./env-config.js touch ./env-config.js # Add assignment echo \"window._env_ = {\" >> ./env-config.js # Read each line in .env file # Each line represents key=value pairs while read -r line || [[ -n \"$line\" ]]; do # Split env variables by character `=` if printf '%s\\n' \"$line\" | grep -q -e '='; then varname=$(printf '%s\\n' \"$line\" | sed -e 's/=.*//') varvalue=$(printf '%s\\n' \"$line\" | sed -e 's/^[^=]*=//') fi # Read value of current variable if exists as Environment variable value=$(printf '%s\\n' \"${!varname}\") # Otherwise use value from .env file [[ -z $value ]] && value=${varvalue} # Append configuration property to JS file echo \" $varname: \\\"$value\\\",\" >> ./env-config.js done < .env echo \"}\" >> ./env-config.js For our local development we would provide a dummy .env file with the following content. SITE_URL=# SITE_STATUS=Accepted Next, we add another entry dev under the scripts configuration in the file package.json, like this. ... \"scripts\": { \"start\": \"react-scripts start\", \"build\": \"react-scripts build\", \"test\": \"react-scripts test\", \"eject\": \"react-scripts eject\", \"dev\": \"chmod +x ./env.sh && ./env.sh && mv env-config.js ./public/ && react-scripts start\" }, ... And from now on, we would launch our local development server with this command $ npm run dev instead of the previously used npm run start. That script entry executes the env.sh bash script to freshly generate the file env-config.js and copies it into the public folder of the React app where the index.html picks it up. Finally, we amend the launching command statement of the production-stage in the Dockerfile. ... EXPOSE 80 # CMD [\"nginx\", \"-g\", \"daemon off;\"] CMD [\"/bin/bash\", \"-c\", \"/usr/share/nginx/html/env.sh && nginx -g \\\"daemon off;\\\"\"] This way we generate environmental information into the window._env_ JavaScript object in our React app each time the image is launched, and before nginx web server is executed. In Google Kubernetes Engine we are offered to specify Environment variables for use in the cluster. Changing any value of a environmental variable does not require a full build and deploy circle anymore but a mere termination and start of the pod running the React app. Play with docker-compose While working on the implementation of using environmental variables I played a little with composer and created a docker-compose.yml file. However, this didn't give me the expected outcome and the file is abandoned in the repository. If you have any hints how this could be an useful approach let me know in the comment section below the article. Visual Studio Code and Docker Last but least, a few recommendations to extend Visual Studio Code with some tools to ease your work using Docker, YAML files and probably remote environments: Docker - see screenshot below docs-yaml or YAML with built-in Kubernetes syntax support Remote Development - a meta package for three extensions GitHub repository Okay, one last one... All information and instructions steps, all files inclusive changes over time are available in the repository Google Africa Developer Scholarship Phase II - Google Cloud Challenge I on GitHub. You're most welcome to fork/clone the repository and apply your personal touch to the React app. Thanks for reading that far. Kindly leave a comment or response. Image credit: Alex Motoc if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-ready.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-ready'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-cloud-run.html": {
    "href": "alc4-cloud-run.html",
    "title": "Using Cloud Run instead of GKE - Get Blogged by JoKi",
    "summary": "Using Cloud Run instead of GKE Copy Markdown View Markdown GitHub Markdown Source Using Cloud Run instead of GKE Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-run'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-cloud-run'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Using Cloud Run instead of GKE') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-11-26 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Google Cloud Platform offers another service to operate containers called Cloud Run. It brings the idea of serverless computing to your containers. Instead of being limited to the available support for languages, libraries and code restrictions in Cloud Functions you can create and run your own container in a similar, serverless fashion. Compared to Kubernetes Engine, Cloud Run abstracts the overhead of administration and enables you to deploy your application almost instantly. Cloud Run is a managed compute platform that automatically scales your stateless containers. Cloud Run is serverless: it abstracts away all infrastructure management, so you can focus on what matters most — building great applications. Find out more information about Cloud Run. Taking the React app to Cloud Run Similar to the deployment to Google Kubernetes Engine (ALC 4.0 Cloud Challenge I) you would want to deploy the existing React app to Cloud Run. The following command allows you to use any of the available containers in Container Registry (or Docker) and launch it on Cloud Run. > gcloud run deploy --image gcr.io/alc-4-program/alc4cloud:cloudbuild If you do not specify a service name, platform, region, or access mode the command is going to prompt you for the missing information. Here is the complete command with all mandatory switches for easier handling. > gcloud run deploy alc4cloud \\ --image gcr.io/alc-4-program/alc4cloud:cloudbuild \\ --platform=managed \\ --region=us-central1 \\ --allow-unauthenticated After a short while the command output is most probably going to respond with the following information. ... Deployment failed ERROR: (gcloud.run.deploy) Cloud Run error: Container failed to start. Failed to start and then listen on the port defined by the PORT environment variable. Logs for this revision might contain more information. Oh, what happened here? Your container is in perfectly good shape but still Cloud Run rejects the deployment due to the inability to listen on the port defined by the PORT environment variable. RTFM - Read The Fine Manual According to the documentation on Building Containers for Cloud Run container images are accepted as long as they respect the container contract. In particular, your code must listen for HTTP requests on the port defined by the PORT environment variable. Okay, then let's have a look. To learn more about the contract your containers must respect to be deployed to Cloud Run, see Container Contract. In Cloud Run container instances, the PORT environment variable is always set to 8080, but for portability reasons, your code should not hardcode this value. How is this related to your container running the React app? Following the article Working with Docker (ALC 4.0 Cloud Challenge I) you wrote a Dockerfile to build containers using nginx webserver. Using the default configuration and listening on port 80. That's a violation to the container contract as per specification and you have to change the implementation of the Docker image. Modifying the container In order to respect the container contract for Cloud Run you need to amend the existing Dockerfile slightly. There are two changes needed. First, you have to change the Listen directive in the default configuration of nginx. An elegant approach would be to use sed - the stream editor for filtering and transforming text - in the Linux system used to run the React app. Add the following RUN statement to the production-stage in the Dockerfile. RUN sed -i 's/80/8080/g' /etc/nginx/conf.d/default.conf Next, you have to change the exposed port of the container to grant access to it from external sources. Change the EXPOSE line like so. EXPOSE 8080 The adjusted Dockerfile should have the following content. # build stage FROM node:lts-alpine as build-stage RUN npm i -g react-scripts WORKDIR /app COPY package*.json yarn.lock ./ RUN npm ci COPY . ./ RUN npm run build # production stage FROM nginx:stable-alpine as production-stage RUN apk add --no-cache bash COPY --from=build-stage /app/build /usr/share/nginx/html COPY env.sh /usr/share/nginx/html COPY .env /usr/share/nginx/html RUN chmod +x /usr/share/nginx/html/env.sh WORKDIR /usr/share/nginx/html # Cloud Run requires port 8080. Let's change nginx... RUN sed -i 's/80/8080/g' /etc/nginx/conf.d/default.conf EXPOSE 8080 CMD [\"/bin/bash\", \"-c\", \"/usr/share/nginx/html/env.sh && nginx -g \\\"daemon off;\\\"\"] Now, your local source is ready for Cloud Run. Submit and Run Either you would go forward to create the new container on your local system according to the article Working with Docker or you might like to send the sources as described in Working with Cloud Build. Being smart you submit to Cloud Build using this command. > gcloud builds submit -t gcr.io/alc-4-program/alc4cloud:cloudrun . Although it is the same React app you should give the container image a new tag, here cloudrun to differentiate it a little bit from the previously created images. Note: I put a file called Dockerfile.Cloudrun into my repository on GitHub for reference. Unfortunately, you cannot specify a different file when submitting to Cloud Build. You might have to rearrange the files if you want to use it. After successful submission to Cloud Build your Container Registry might look a little like the following. Reflecting all tagged images built so far. With the containerized image available in the registry it's now time to deploy to Cloud Run using the gcloud run command. > gcloud run deploy alc4cloud \\ --image gcr.io/alc-4-program/alc4cloud:cloudrun \\ --platform=managed \\ --region=us-central1 \\ --allow-unauthenticated \\ --set-env-vars=SITE_URL=\"https://jochen.kirstaetter.name\",SITE_STATUS=\"Cloud Run\" Note: Amend the tag value according to the image you built. Setting the environment variables might not be necessary depending on your implementation. The deployment is going to take a few minutes. Maybe you like to stretch your legs, grab a glass of water, and have a cereal bar to re-energize yourself while waiting. Cloud Run will respond with a simple Done. and provides you with the URL to run your stateless container on the web. Awesome, well done! You have managed to adjust the Docker image to listen to port 8080 and deployed the container to Cloud Run. Same steps in Cloud Console In case that you feel more comfortable in Cloud Console than typing commands in the terminal, you can navigate to Cloud Run in the sidebar menu. There you click on Create Service to set up a new service operating on Cloud Run. Similar to the gcloud command you specify the container image, the preferred platform to deploy inclusive a location, give the service a name and choose the authentication mode. Then you hit the Create button and wait until the service has been created. Shortly after the endpoint will be accessible via exposed URL and the service metrics are shown in Cloud Console. The choice is yours. Remember that the container image has to respect the container contract and listen to port 8080. Otherwise, deployment is going to fail. Pricing of Cloud Run At the time of writing the following price structure applies to Cloud Run. A small project like the React app could be an interesting candidate to deploy and run on Cloud Run. Depending on the number of visitors and hence invocations per month it might be very affordable, too. Cloud Run has HTTPS built-in Each Cloud Run service gets an out-of-the-box stable HTTPS endpoint, with TLS termination handled for you. Using your own domain You might have seen that the auto-generated URL Cloud Run allocates to your service isn't the most user-friendly. Conveniently, Cloud Run offers custom domains out-of-the-box. On the Cloud Run service overview click on the triple dot button and select Manage Custom Domains to map your own domain for your service. Next, click on Add Mapping in case that you are not greeted by the Add mapping dialog already. In case that your domain is not listed as a verified domain you are offered to do it on the spot. Wait until the verification process has been completed successfully and then specify the domain to use. Cloud Run is able to map your service to the base domain as well as to a subdomain record. Finally, Cloud Run gives you details on how to update the DNS records of your custom domain. This step completes the mapping to your service. It takes about five to six minutes until the domain mapping is done. However, give it some more time until the Cloud Run service responds to your custom domain as expected. See for yourself here: https://alc4cloud.kirstaetter.name/ And you're done, again. Check out the documentation on Mapping custom domains for more details and background information on this matter. Enable Cloud Run API The Cloud Run API is not enabled by default in your GCP project. The gcloud command will prompt you accordingly and allows you to activate it. Confirm the question with Y and wait as instructed to retry the submission. Of course, you could also navigate around in APIs & Service > Library in the Cloud Console. Enable both Cloud Build and Cloud Run APIs. Using Cloud Run as a substitute for Kubernetes Engine has its charm. It offers a faster path to develop, deploy and operate with less administrative overhead. Additionally, Cloud Run provides a richer environment for your applications based on your custom-made stateless container compared to the pre-defined language runtime provided by Cloud Functions. Apart from adjusting the container to listen to the default port 8080 as per container contract and optionally mapping a custom domain it is an almost effortless workflow to deploy a container image to Cloud Run and make it publicly accessible. What do you think? Do you have use-cases where Cloud Run is the right choice for you? Leave a comment below and start the conversation with me and other readers. Image credit: Fitsum Admasu if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-cloud-run.html'; this.page.identifier = '9271ed1c97_idalc4-cloud-run'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-qwiklabs.html": {
    "href": "alc4-qwiklabs.html",
    "title": "ALC 4.0 Cloud - Access to Qwiklabs - Get Blogged by JoKi",
    "summary": "ALC 4.0 Cloud - Access to Qwiklabs Copy Markdown View Markdown GitHub Markdown Source ALC 4.0 Cloud - Access to Qwiklabs Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-qwiklabs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-qwiklabs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ALC 4.0 Cloud - Access to Qwiklabs') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-09-04 - Filed under CommunityAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The following collection of hands-on-labs on Qwiklabs has been made available to scholars of the Andela Learning Community (ALC) 4.0 program. A collaboration between Andela, Google and Pluralsight to grow with Google technology. The links require that you're logged into your account on Pluralsight. Eventually, the links might stop to work after expiration of the program in January 2020. Alternatively, you can go directly to Qwiklabs and create an account on their platform. Although phase 1 of the program has been concluded already I should have created the following set of URLs some weeks ago. Hence the article is more of a bookmark for my studies towards the Associate Cloud Engineer exam than some instructions. Not sure whether it's going to be helpful for anyone outside the ALC 4.0 program. Feedback is most welcome in the comment section below. Google Cloud Platform Fundamentals: Core Infrastructure Getting Started with Google Cloud Platform Lab: Getting Started with Cloud Launcher Note: Cloud Launcher has been renamed into Marketplace Virtual Machines and Networks in the Cloud Lab: Getting Started with Compute Engine Storage in the Cloud Lab: Getting Started with Cloud Storage and Cloud SQL Containers in the Cloud Lab: Getting Started with Kubernetes Engine Applications in the Cloud Lab: Getting Started with App Engine Developing, Deploying, and Monitoring in the Cloud Lab: Getting Started with Deployment Manager and Stackdriver Big Data and Machine Learning in the Cloud Lab: Getting Started with BigQuery Essential Cloud Infrastructure: Foundation Introduction to Google Cloud Platform Infrastructure Lab: Console and Cloud Shell Lab: Infrastructure Preview Virtual Networking Lab: Virtual Networking Lab: Bastion Host Virtual Machines Lab: Creating Virtual Machines Lab: Working with Virtual Machines Essential Cloud Infrastructure: Core Services Cloud IAM Lab: Cloud IAM Data Storage Services Lab: Cloud Storage Lab: Cloud SQL Lab: Datastore Resource Management Lab: Examining Billing Data with BigQuery Resource Monitoring Lab: Resource Monitoring with Stackdriver Lab: Error Reporting and Debugging with Stackdriver Elastic Cloud Infrastructure: Scaling and Automation Interconnecting Networks Lab: Virtual Private Networks (VPN) Load Balancing Lab: Virtual Machine Automation and Load Balancing Autoscaling Lab: Autoscaling Infrastructure Automation with Cloud API Lab: Google Cloud Platform API Infrastructure Automation Infrastructure Automation with Deployment Manager Lab: Deployment Manager Elastic Cloud Infrastructure: Containers and Services Containers Lab: Kubernetes Load Balancing Image credit: Eliabe Costa if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-qwiklabs.html'; this.page.identifier = '9271ed1c97_idalc4-qwiklabs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "alc4-slack-questions.html": {
    "href": "alc4-slack-questions.html",
    "title": "ALC 4.0 Cloud - Slack questions - Get Blogged by JoKi",
    "summary": "ALC 4.0 Cloud - Slack questions Copy Markdown View Markdown GitHub Markdown Source ALC 4.0 Cloud - Slack questions Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-slack-questions'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'alc4-slack-questions'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ALC 4.0 Cloud - Slack questions') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-08-10 - Filed under CommunityAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); A collection of questions that popped up in Slack recently. I'm going to give my opinion based on the knowledge I have gained so far. Surely, this is not going to be complete and there are different experiences and points of view. Feel free to join the conversation in the comment section below. The Questions... I got some questions if you don't mind What do you think of Microsoft Azure, AWS compared to GCP? (services they provide, security, availability e.t.c) Since you have an MVP in Azure. Since I see in your bio that you're a software craftsman: 2.Could you please share your journey to software engineering? What advises you will give to people who are getting started in this field? If you're starting this journey today, what are the roadmaps or things you will do and don't do to becoming a software engineer? Update 03. September 2019 What can you say about the time you spent learning Microsoft Azure and the time you are now spending learning GCP? Azure or AWS or GCP? Regarding Q1 I would like to clarify first that I have been awarded Microsoft MVP in the category Developer Technologies. Additionally, I have successfully passed various exams regarding Microsoft Azure. Coming back to the actual question. All cloud infrastructures are great and they all offer immense opportunities, features and functionality to address any kind of software related project. Personally, I'm quite familiar with Microsoft Azure given the experience gained in contracted work for customers as well as a number of experiments over the past years. We have successfully conducted Lift & Shift operations for multiple clients. The flexibility of cloud infrastructure and services is superior in general. As for Google Cloud Platform (GCP) I have to admit that it is only since my participation in the ALC 4.0 program that I'm exploring this cloud platform. Before that, I did Azure only. My observations and activities in using GCP compared to Azure have been positive so far. My main obstacle at the beginning was to get into the naming conventions, eg. services and their features, and into the taxonomy of each. Overall it's a bit different to Azure - in a neutral sense - and it took me some time to disconnect my way of thinking and approaching certain aspects from Azure. Sometimes, I'm missing the Azure blade functionality in the web application but then it's solely about using the CLI and template approach to work with any cloud in a professional manner. Using Cloud Shell or the Cloud SDK locally gives you a sleek and powerful access point to operate your cloud resources. Tools like gcloud, gsutil and bq are really powerful and with the extensibility of components they give you a high grade of flexibility, too. Which is similar to Azure's tools, namely the PowerShell cmdlets and azcli. Speaking of Amazon Web Services (AWS), well, I have to pass on this one. Until now, I did not explore any cloud services on AWS yet. Okay, I'm aware of features like S3 and others but I did not use of those AWS services in a contracted project or for any side-gig fun stuff. Looking forward to change that some day. To wrap up my answer I would say that I'm currently still biased towards Azure. Although there are some aspects in GCP, like networking and resource tagging, that seem to be more intuitive and easier to use as it abstracts a lot of the design aspects of infrastructure planning from the user. It's a high level view on the project and it seems to work more smooth than resource groups in Azure. Either way, my advice would be to explore all available cloud options and not to restrict yourself or your application to a single vendor. Given an architecture of microservices one should consider that they can run across multiple clouds in order to provide the best service and features compared to sticking to a single choice. My journey into software engineering Answering Q2 is a short journey down memory lane as I officially started working as a professional software developer in April 1999. Although I was already writing software; mainly web development, back then already with JavaScript (and JScript) and bash scripts before that date, it marks the beginning of being a paid software developer. And for the record, I never attended any formalised courses on software engineering or computer science at the university. Actually, I studied a few years of applied chemistry at the university before a lucrative job offer and cloudy future job prognosis in the chemical industry changed my mind. From then on I enjoyed the daily learning experience and the satisfaction to actually create something other people would use in their daily activities. I have been involved in various customer-specific software projects that partly are still running nowadays. Around 2001 I got more involved with user groups and software communities. This and the development of a new product launch at my then employer also kicked off my activities in public speaking in 2002. With increasing passion, deepening knowledge of the programming language and ongoing assistance in public forums I gratefully have been awarded Microsoft Most Valuable Professional (MVP) first in 2006. Learning never stopped for me and it's my motivation to discover something new every day. How to get started in this field? Hmm, this is very personal and I can only share my own experience. Perhaps this might give you an indication what to look for... First of all, find your inner voice and passion. Independently whether it is for a certain programming language, a field of technology, or solving problems in general to improve your or others daily struggle. It's that passion and drive that keeps you going every single day. And gives you the motivation to excel your own boundaries. Speaking of which, you should never doubt your dreams and ideas. They are achievable for you, maybe it might take a bit more time than anticipated, but keep going and you might be surprised about how much you can achieve over a certain period with continuous effort. Start with the fundamentals. Get a solid understanding of good practices and principles in the field. And in this case I'm actually talking about the following: Learn how to touch-type. Seriously, with increased speed to manifest your thoughts into the computer you'll find it more satisfying at the end of the day than having to search every keystroke. Develop the ability to read information properly Referring back to the ALC 4.0 program and the amount of screenshots with clear error messages that have been posted, gave me to think about this trade. Explore additional references Don't stop at the material in front of you. Look for referred sources and additional content to dig deeper into a specific technology or concept. Do not settle with a status quo but ask for more. In lack of information run a search on relevant keywords and enjoy thousands of resources, ie. blog articles, documentation, forum threads, etc. Practice, practice, and practice more None of the grandmasters in any field were born with the ability to do great things. They all started at the basics and went through years of scrutinizing practice. This procedure also separates you from the mere mortal peers. Practice your skills every day. See yourself as a computer athlete and you might understand the approach. Last but not least, don't let others tell you what you're capable of or not. The only frontiers are in your mind. It is only you that possibly would be able to limit yourself. Nobody else. A revised roadmap There is no universal answer to Q4 though. Again, I'm going to answer this given my own experience so far. If I would start a career in software engineering today... I would lay out an actionable plan towards a very specific achievement. And then trace back all activities and effort involved into reaching that goal. Let's take my participation in the ALC 4.0 program as a reference. As described in answer #1 I joined the program for one particular reason only: To start with and learn more about the Google Cloud Platform. The current ultimate goal is to sit and pass at least one Google exam and to achieve a specific certification. Access to the video courses on Pluralsight and the sponsored access to the Qwiklabs are the foundation of this roadmap. Over the past weeks, I noticed that some of my knowledge in Microsoft Azure and Linux in particular transpires nicely into GCP. This gave me a certain advantage compared to other learners in regards that I could focus more on the finer differences and advantages GCP offers than having to deal, read: struggle, with the basics of using Linux commands in Google Cloud Shell. This means that creating a proficient knowledge of Linux should be placed onto that roadmap we started. According to a statement by Scott Hanselman back in October 2018 more than 50% of services on Azure are operated on Linux. Microsoft: “50% of Azure runs Linux.” Also Microsoft: releases DOS as open source under MIThttps://t.co/ETq4onW4Cs Clearly there’s only one conclusion! CLOUD-SCALE CONTAINERIZED DOS AS A SERVICE Y’ALL — Scott Hanselman (@shanselman) October 3, 2018 Given the free (as in beer) access to the various Linux distributions one should definitely venture deeper into that area and sharpen a number of skills on the terminal. This shall be part of your fundamental knowledge in cloud computing. Apart from this, I would again get in touch with local, regional and global user groups. The ability to meet on- and offline with other like-minded people in the field is invaluable to your career. You learn most by observing and watching other more experienced developers / engineers. They share their experiences and help you to avoid a good number of pitfalls in your personal growth. In case there is none in your surrounding reach out global to associations, like Google Developer Groups (GDG), .NET Foundation, Microsoft, Mozilla, Atlassian, openSUSE, etc. and probably start your own group. You might be surprised how this could evolve into something larger and beyond your best imagination. Dream big and go for it. And perhaps I might want to join at least one open source software (OSS) project on the internet, if time allows of course. For sure I wouldn't settle with the ordinary. Whatever it might be, say a course curriculum, studies, a project assignment. Always strive to not only complete but to excel a task. PS: FYI, I had to learn this kind of attitude. \uD83D\uDE09 Time spent learning For Azure it was an organic transition due to my daily software development activities. We started to develop solutions for Azure infrastructure and services. Some of our customers were interested to explore options to reduce cost and to modernize their IT landscape. It was a pragmatic approach to get work done. Compared to the ALC 4.0 program where I'm solely exploring GCP and experimenting with the labs without having any actual business cases or customer requirements for the moment. As an organiser of GDG Mauritius however I'm looking forward to be able to host meetings on Google Cloud and perhaps run a few Cloud Study Jams in the weeks to come. Image courtesy: Perry Grone if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'alc4-slack-questions.html'; this.page.identifier = '9271ed1c97_idalc4-slack-questions'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "all-saints-day-oder-auch-nicht.html": {
    "href": "all-saints-day-oder-auch-nicht.html",
    "title": "All Saints' Day - oder auch nicht... - Get Blogged by JoKi",
    "summary": "All Saints' Day - oder auch nicht... Copy Markdown View Markdown GitHub Markdown Source All Saints' Day - oder auch nicht... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'all-saints-day-oder-auch-nicht'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'all-saints-day-oder-auch-nicht'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'All Saints' Day - oder auch nicht...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-01 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wir befinden uns im Jahr 2006 n. Chr. Ganz Deutschland ist von den Urlaubern besetzt ... Ganz Deutschland? Nein! Eine von unbeugsamen Entwicklern bevölkerte Firma hört nicht auf, dem Kunden Quellcode zu liefern so, oder ähnlich könnte man den heutigen Tag einleiten. Statt ein wenig den Eskapaden vom gestrigen Halloween zu fröhnen, gibt's Quellcode in der Firma. Und dass nicht zu wenig. Okay, Allerheiligen ist nicht überall gleichermaßen ein Feiertag (oder?) und wir sind nicht aus Zucker. Zumal... ist der freie Tag lediglich um ein paar Tage verschoben. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'all-saints-day-oder-auch-nicht.html'; this.page.identifier = '9271ed1c97_idall-saints-day-oder-auch-nicht'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "already-3333-tweets.html": {
    "href": "already-3333-tweets.html",
    "title": "Already 3,333 tweets... - Get Blogged by JoKi",
    "summary": "Already 3,333 tweets... Copy Markdown View Markdown GitHub Markdown Source Already 3,333 tweets... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'already-3333-tweets'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'already-3333-tweets'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Already 3,333 tweets...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-08-20 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Almost everything is about numbers, and this short blog entry includes the number of tweets written so far. \uD83D\uDE09 Quite frankly, I joined the Twitter bandwagon late... February 2010 or so. And within those 4.5 years I just came across an interesting milestone: 3,333 tweets 3,333 tweets since February 2010 Compared to some of my fellows here from Mauritius that's almost nothing... Anyways, Cheers and for the next 3,333 tweets. This time faster, hopefully. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'already-3333-tweets.html'; this.page.identifier = '9271ed1c97_idalready-3333-tweets'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "am-chiemsee-im-winter.html": {
    "href": "am-chiemsee-im-winter.html",
    "title": "Am Chiemsee im Winter - Get Blogged by JoKi",
    "summary": "Am Chiemsee im Winter Copy Markdown View Markdown GitHub Markdown Source Am Chiemsee im Winter Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'am-chiemsee-im-winter'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'am-chiemsee-im-winter'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Am Chiemsee im Winter') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2007-01-26 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Well, eigentlich dürfte diese Region Deutschlands um Neujahr eigentlich mehr für weiße Landschaft, gefrorene Seepartien und vielleicht Eislaufen und/oder Skilanglauf bekannt sein, aber bei annähernd sommerlichen Temperaturen \uD83E\uDD2A sieht es dann doch wieder ganz anders aus, als man annimmt. Direkt am Neujahrstag machte ich mich auf, um mal wieder in unserem Büro in Süddeutschland ein wenig Unwesen - aka Code schreiben - zu treiben. Nun gut, das Jahr 2007 startet ja gleich wieder richtig gut. Apropos gut... wer hat eigentlich den Beitrag über die Verlässlichkeit der Deutschen Bahn hier in diesem Blog geschrieben? Der Typ dürfte echt keine Ahnung gehabt haben... Nee nee nee... Zumindest war meine Zugreise diesmal eher eine Odysee. Und eigentlich hatte es noch einigermaßen akzeptabel in Kaiserslautern begonnen, aber irgendwie mochte mich das Online-Ticket-Buchungssystem der DB nicht so wirklich. Die Werbung verspricht zumindest, dass man bis etwa 10 Minuten vor Fahrtantritt für die gewünschte Strecke seine Fahrkarte online buchen kann, eh, könnte... Denn irgendwie mochte mich der Login nicht... Korrekte Daten eingegeben (scheinbar korrekt) und prompt nach dem zweiten Versuch wegen irgendwelcher Refreshprobleme mal fett für 30 Minuten aus dem System gesperrt worden. Hm, okay, no problem, bis Heidelberg dauert's etwa 'ne Stunde und zumindest für den EC wäre 'ne Fahrkarte optimal. Nun, nach besagten 60 Minuten immer noch kein Login (geschweige denn Passwortanforderung) gestattet. Öhem, okay... schauen wir mal weiter... Nach fast 2 Stunden konnte ich mich endlich einloggen und wollte nachlösen, aber ätsch, angeschmiert, geht nicht... Öhem, okay, dann eben aktueller Bahnhof und Ticket ordern, nix da... Nunja, letztendlich ging's dann über den klassischen Weg beim Schaffner, äh Zugbegleiter, bei dem ich dann nach gut 4/5tel der Fahrtstrecke eine Fahrtkarte rückwirkend von Kaiserslautern aus gelöst habe - ja, so ehrlich bin ich... Abends dann noch im Hotel um ein Abendessen gefeilscht und ab in die Federn. Zu sehen gab's eh nichts um die Uhrzeit... Die nächsten drei Tage zusammen mit den Kollegen könnte man schon fast als 'ne Art CodeCamp bezeichnen. Wir hatten unseren Schulungsraum komplett in Beschlag genommen und nur zum Erledigen der notwendigsten Dinge des Lebens ging mal jemand kurz raus. Okay, ganz schlimm war's nun auch wieder nicht, da wir Mittag- und Abendessen außerhalb des Büros zu uns nahmen. Eine richtig coole Zeit mal wieder mit den Kollegen zusammen zu sitzen, zu debattieren und Code zu produzieren. Die sonstige Zeit des Jahres tauschen wir uns meist über Skype, Telefon und Mail aus... auch okay, aber offline macht einfach mehr Spass. Öhja... Chiemsee... trotz des warmen aber nassen Wetters immer wieder ein herrlicher Anblick mit den Alpen im Hintergrund. Hachja, rein landschaftlich kann man es in Seebruck sicherlich gut aushalten, aber nicht unbedingt in Bezug auf Unterhaltung und Party... \uD83D\uDE42 Und ja, ganz weit oben an den Berghängen konnte man schwach so 'ne weiße Masse erkennen. Ich meine, dass es als Schnee bezeichnet wurde... Früher... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'am-chiemsee-im-winter.html'; this.page.identifier = '9271ed1c97_idam-chiemsee-im-winter'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "and-again-vmware-player-404-on-ubuntu-1204-precise-pangolin.html": {
    "href": "and-again-vmware-player-404-on-ubuntu-1204-precise-pangolin.html",
    "title": "And again... VMware Player 4.0.4 on Ubuntu 12.04 (Precise Pangolin) - Get Blogged by JoKi",
    "summary": "And again... VMware Player 4.0.4 on Ubuntu 12.04 (Precise Pangolin) Copy Markdown View Markdown GitHub Markdown Source And again... VMware Player 4.0.4 on Ubuntu 12.04 (Precise Pangolin) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'and-again-vmware-player-404-on-ubuntu-1204-precise-pangolin'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'and-again-vmware-player-404-on-ubuntu-1204-precise-pangolin'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'And again... VMware Player 4.0.4 on Ubuntu 12.04 (Precise Pangolin)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-06-15 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Even with the new version of VMware Player 4.0.4 you are still required to patch their sources. So, same game as last month. Just changing the value of the required version in the kernel patch script of Stefano Angeleri (weltall) and you are done: nano patch-modules_3.2.0.sh Please change line 8 at the top of the script like so: plreqver=4.0.4 Save your modification and then run the following commands: sudo rm /usr/lib/vmware/modules/source/.patched sudo ./patch-modules_3.2.0.sh sudo service vmware restart And again, on first start VMware will present you their EULA which you have to accept, and everything gets back to normal operation mode. Interestingly, one day ago the Ubuntu repositories provided a newer kernel version 3.2.0-25, so everything is running fine for now. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'and-again-vmware-player-404-on-ubuntu-1204-precise-pangolin.html'; this.page.identifier = '9271ed1c97_idand-again-vmware-player-404-on-ubuntu-1204-precise-pangolin'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "andy-volk.html": {
    "href": "andy-volk.html",
    "title": "Dinner with Andy Volk - Get Blogged by JoKi",
    "summary": "Dinner with Andy Volk Copy Markdown View Markdown GitHub Markdown Source Dinner with Andy Volk Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'andy-volk'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'andy-volk'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Dinner with Andy Volk') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-03-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); While exchanging with the nice GDG folks over at Google Nigeria about the possibility to get Google interested to assist during the annual Developers Conference of the MSCC, some magic happened again. In an email response with my GDG leads at Google, Aniedi came up with an information that Andy Volk would be in Mauritius from 6th to 8th of March for a Google-led event. Hence he suggested to meet with Andy over dinner and share your experience, insights, challenges and opportunities for developers in Mauritius. Of course, you don't have to ask me twice for such an opportunity. A few days and emails later it was set to have dinner with Andy Volk, Google Developer Relations, Sub-Saharan Africa. Generously, Andy asked to get hooked up with a few other community organisers to learn about the developer ecosystem and entrepreneurship landscape in Mauritius. Given this outstanding opportunity I reached out to some folks and after a few days of communication the following list was set for the dinner evening at Flying Dodo: Yovan Fowdar (Mauritius Maker Community) Chervine Bhiwoo (PASS) Sandeep Ramgolam (Front-End Developers) Vincent Pollet (Founder of ict.io) Yusuf Satar (Linux User Group of Mauritius) Logan Velvindron (hackers.mu) and me, Jochen Kirstätter (GDG Mauritius and MSCC) That line-up changed slightly. Unfortunately, Logan couldn't attend due to some health issues and Avinash Meetoo, Senior Advisor to the Minister of Technology, Communication and Innovation, joined us at a later stage. At agreed time, Yovan was already expecting us and Andy joined us a bit later as he had to attend a rehearsal session for the Africa Internet Academy happening during the next few days. Andy introduced himself and gave us an overview of his activities and involvements in Africa, and that he'd interested to get to know a bit more about the software development and entrepreneur scenery in Mauritius. It was rally interesting to hear about the Launchpad Africa acceleration program for the region, how the game development scene in South Africa has evolved and contributes to the country's GDP. Those two hours went too quickly, and it would have been a pleasure to listen more to Andy's activities in the Sub-Saharan Africa region. I'm hooked up for more content and engagement with Google during the coming months. Simply because I love the cross-platform experience and I'd like to broaden my options as a professional software developer. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'andy-volk.html'; this.page.identifier = '9271ed1c97_idandy-volk'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "angrycoder.html": {
    "href": "angrycoder.html",
    "title": "angryCoder - Get Blogged by JoKi",
    "summary": "angryCoder Copy Markdown View Markdown GitHub Markdown Source angryCoder Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'angrycoder'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'angrycoder'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'angryCoder') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-06 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Yeah Baby!! This site kicks ass! Puuuh, was hab' ich in den Minuten gelacht. Leute, die Artikel beim angryCoder rocken das Haus. Ich find's absolut genial, was da geschrieben wird. Die Jungs schreiben mir wirklich vom Herzen. Ein Artikel hat mir besonders gefallen - bisher - nämlich jener: Standards? We Don't Need No Steenking Standards! - https://www.angrycoder.com/article.aspx?cid=1&y=2004&m=2&d=17 Ich mag echt nicht mehr die Jahre zählen seit denen ich die gleiche Meinung habe und auch vertrete. Gerade Microsoft in der Position des Führenden - But just because they're the king of the hill doesn't mean we can allow [...] - hachja... Die Artikel sind wirklich herzerfrischend! Gleich mal deren URL fest in meinen daily track einbezogen. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'angrycoder.html'; this.page.identifier = '9271ed1c97_idangrycoder'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "angularjs.html": {
    "href": "angularjs.html",
    "title": "AngularJS by Brad Green and Shyam Seshadri - Some lack of quality - Get Blogged by JoKi",
    "summary": "AngularJS by Brad Green and Shyam Seshadri - Some lack of quality Copy Markdown View Markdown GitHub Markdown Source AngularJS by Brad Green and Shyam Seshadri - Some lack of quality Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'angularjs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'angularjs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'AngularJS by Brad Green and Shyam Seshadri - Some lack of quality') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-01-18 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); New year, new review...Thanks to O'Reilly blogger review programme I had a chance to put my hands on their title on AngularJS by Brad Green and Shyam Seshadri. This time, a quick and crisp review as published on Amazon: Some lack of quality I spent quite some time with this book title from O'Reilly. First, it's an interesting topic and second, I got the 'Early Release' version and later then the full version of the book. Personally, I think the book could have been a good start for beginners but I have to admit that while digging deeper into the chapters I got more and more confused about the actual learning goal. Yes, the general overview on AngularJS is easy to follow and helps to understand how AngularJS is organised. But then somewhere around half way through it's getting more and more tough to actually 'get' the authors' intention. Additionally, the code samples are getting off-track and quite often there syntactical errors in the code samples. Given the fact that modern JavaScript development, which includes AngularJS, is fast-paced and dynamic I was kind of surprised by this lack of quality. This title leaves some mixed feelings on my opinion. Would I recommend it to someone else to read? - It depends on the level of knowledge the other person might already have with AngularJS, HTML5 and JSON. Quite frankly, I read very well into the first couple of chapters but then it got 'chewy'. From time to time it took me quite some motivation to continue to read. Nonetheless, the book title is nice addition on my digital bookshelf and I might use it as a reference in future cases. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'angularjs.html'; this.page.identifier = '9271ed1c97_idangularjs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "anmerkungen-zum-decorator.html": {
    "href": "anmerkungen-zum-decorator.html",
    "title": "Anmerkungen zum Decorator Design Pattern - Get Blogged by JoKi",
    "summary": "Anmerkungen zum Decorator Design Pattern Copy Markdown View Markdown GitHub Markdown Source Anmerkungen zum Decorator Design Pattern Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'anmerkungen-zum-decorator'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'anmerkungen-zum-decorator'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Anmerkungen zum Decorator Design Pattern') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-01 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Anscheinend bedarf es der regelmäßigen Wiederholung von Themen, die man im täglichen Einsatz als Entwickler gebrauchen könnte. Wiederholung vertieft das Wissen und schadet sicherlich nicht. Die nachfolgenden Anmerkungen beziehen sich auf meinen Artikel zum Design Pattern: Decorator. Es empfiehlt sich den Beitrag kurz durchzugehen, sofern der Decorator nicht geläufig sein sollte. Den Auslöser für diesen Blogeintrag liefert mal wieder eine Unterhaltung im Forum der dFPUG. Dort wurde sinngemäß das Problem formuliert wie man gemeinsame Funktionalität zweier unterschiedlich abgeleiteter Klassen ohne Codedopplung umsetzen könnte, aber dennoch die sonstigen PEMs der Hierarchie erhalten kann. Da es in Visual FoxPro das Konzept der Mehrfachvererbung - Stichwort partial class in C# - leider nicht gibt, sehen die Alternativen zur Umsetzung ein wenig anders aus: [ulist] Code-Dopplung (wollen wir ja nicht) Funktionalität in aggregierte Klasse auslagern (Zugriff auf Eigenschaften/Methoden erschwert) Funktionalität durch Klasse vorschalten (Decorator) [/ulist] Sofern ich den Original-Thread korrekt verstanden habe, erscheint mir die Anwendung des Decorator an sinnvollsten, da wir hier entsprechende Funktionalität ohne Code-Dopplung erreichen können. Wir lagern also die 'gemeinsame' Methode für die Klassen in unterschiedlichen Ableitungshierarchien in eine eigene Klasse aus, und wenden das Decorator Design Pattern an. Ich versuche es mal mit einem 'konstruierten' Code-Beispiel zu verdeutlichen: *==================================================================== * Anwendung des Decorator Design Pattern für die 'Simulation' von * Mehrfachvererbung. *==================================================================== Clear Set Procedure To Decorator.prg Additive oKatze = CreateObject(\"DemoDecorator\", CreateObject(\"Katze\")) oBambus = CreateObject(\"DemoDecorator\", CreateObject(\"Bambus\")) ? \"Katze:\" ? oKatze.Futtern() ? oKatze.Wachsen() ? oKatze.Futtern() ? ? \"Bambus:\" ? oBambus.Wachsen() ? oBambus.Wachsen() ? oBambus.Wachsen() ? oBambus.Wachsen() ? oBambus.Bluehen() *------------------------------------------------------------------ * Decorator mit der ausgelagerten Funktion für beide Klassen * der unterschiedlichen Ableitungshierachien. *------------------------------------------------------------------ Define Class DemoDecorator As Decorator Of \"Decorator.prg\" Function Wachsen() If Vartype(This.oDecorated) == \"O\" *... gleiches Verhalten für beliebige Objektreferenz. Return \"Höher, und immer höher...\" EndIf EndFunc EndDefine *------------------------------------------------------------------ * Eine Klasse der ersten Ableitungshierarchie * (vereinfachte Darstellung - As <ParentClass> kann beliebig sein. *------------------------------------------------------------------ Define Class Katze As Custom && As CSaeugetier Function Futtern() Return \"Satt...\" EndFunc EndDefine *------------------------------------------------------------------ * Eine Klasse der zweiten Ableitungshierarchie *------------------------------------------------------------------ Define Class Bambus As Custom && As CPflanze Function Bluehen() Return \"Volle Pracht erreicht.\" EndFunc EndDefine Das Ergebnis entspricht nach meinem Verständnis der formulierten Anforderung im Forum-Beitrag. Im weiteren Verlauf kamen noch einige Ideen und Fragen auf: Das greift für alles? Für Properties und Methoden? Nach meinem VFP-Wissen, ja. Sämtliche Zugriffe von außen auf die Objektreferenz werden zuerst in This_Access geleitet. Die dortige Fallunterscheidung muss immer ein Objekt zurückliefern. Dieses könnte ehrlich gesagt auch zur Laufzeit dynamisch aggregiert sein, oder auf irgendeine Objektreferenz zeigen. Hauptsache der Rückgabetyp ist ein Objekt. *Mit Deiner Decorator-Klasse müßte man das eigentlich 2stufig anlegen, auch das ginge ja, da THIS_ACCESS dann kaskadiert. (...) Das verschmelzen der beiden Objekte zu einem ist also nur von außen gesehen perfekt. Es fehlt doch aber eigentlich nur eine oDecorator_Access Methode, die nochmal dass gleiche macht, oder seh ich das falsch? * Nun, ich hatte es im ursprünglichen Artikel zum Decorator nicht probiert. Der nachfolgende Code zeigt, dass eine solche Kaskadierung leider nicht möglich ist: *==================================================================== * (Nicht-)Kaskadierung von mehreren Decorator Design Pattern. *==================================================================== Clear Set Procedure To Decorator.prg Additive Set Procedure To Decorator_Demo.prg Additive oKatze = CreateObject(\"DemoDecorator\", CreateObject(\"Katze\")) oBambus = CreateObject(\"BambusDecorator\", oKatze) oSpatz = CreateObject(\"VogelDecorator\", oBambus) ? oSpatz.Wachsen() ? oSpatz.Wachsen() ? oSpatz.Bluehen() ? oSpatz.Futtern() && Problem! ? oSpatz.Fliegen() *------------------------------------------------------------------ * Kaskadierender Decorator * Decorator-Klasse stammt aus dem Blogartikel zum Design Pattern: * [/Design-Pattern-Decorator](xref:design-pattern-decorator) *------------------------------------------------------------------ Define Class CascadeDecorator As Decorator Of \"Decorator.prg\" Function THIS_ACCESS( tcMember As String ) As Object; HelpString \"Ermöglicht den geswitchten Zugriff auf das eigene bzw. zu dekorierende Objekt.\" Local loReturn If Pemstatus(This, Upper( tcMember ), 5 ) m.loReturn = This Else m.loReturn = This.oDecorated If Not PemStatus(m.loReturn, Upper( tcMember ), 5 ) m.loReturn = m.loReturn.oDecorated * m.loReturn = m.loReturn.oDecorated.&tcMember. EndIf EndIf Return m.loReturn Endfunc Function Wachsen() If Vartype(This.oDecorated) == \"O\" *... gleiches Verhalten für beliebige Objektreferenz. Return \"Höher, und immer höher...\" EndIf EndFunc EndDefine *------------------------------------------------------------------ * Äußerer Decorator *------------------------------------------------------------------ Define Class VogelDecorator As CascadeDecorator Function Fliegen() Return \"Über den Wolken...\" EndFunc EndDefine *------------------------------------------------------------------ * Eine Klasse der zweiten Ableitungshierarchie * Muss nun selbst als Decorator fungieren. *------------------------------------------------------------------ Define Class BambusDecorator As CascadeDecorator && As CPflanze Function Bluehen() Return \"Volle Pracht erreicht.\" EndFunc EndDefine *------------------------------------------------------------------ * Eine Klasse der ersten Ableitungshierarchie * (vereinfachte Darstellung - As <ParentClass> kann beliebig sein. *------------------------------------------------------------------ Define Class Katze As Custom && As CSaeugetier Function Futtern() Return \"Satt...\" EndFunc EndDefine Die Begrenzung wird durch den Parameter an die jeweilige Access-Methode limitiert. Bisher konnte ich lediglich eine Verkettung über maximal 2 Stufen erreichen, danach gibt's Probleme durch den Rückgabewert. Es stellt sich sicherlich auch die generelle Frage, welchen Sinn diese Kaskadierung haben sollte. Ich finde, dass im Falle einer hierarchischen Abhängigkeit ein anderes Design Pattern interessanter sein dürfte: Chain Of Responsibility. Falls es doch jemand gelingen sollte, eine bessere Lösung in Bezug zur Kaskadierung auszutüfteln, bitte ich um Feedback, da es mich trotz meiner bisherigen Bemühungen auf alle Fälle interessiert. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'anmerkungen-zum-decorator.html'; this.page.identifier = '9271ed1c97_idanmerkungen-zum-decorator'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "another-mvp-in-mauritius.html": {
    "href": "another-mvp-in-mauritius.html",
    "title": "Another MVP in Mauritius - Get Blogged by JoKi",
    "summary": "Another MVP in Mauritius Copy Markdown View Markdown GitHub Markdown Source Another MVP in Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'another-mvp-in-mauritius'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'another-mvp-in-mauritius'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Another MVP in Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-03-14 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Cool! Recently, I found out that there is another Microsoft Most Valuable Professional (MVP) in Mauritius. He is nominated in Windows Mobile and writes a lot of information on this topic in his blog about PocketPC. Really interesting and maybe useful for future purposes. Have a look at his blog. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'another-mvp-in-mauritius.html'; this.page.identifier = '9271ed1c97_idanother-mvp-in-mauritius'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "aos-2017.html": {
    "href": "aos-2017.html",
    "title": "aOS Mauritius 2017 - Get Blogged by JoKi",
    "summary": "aOS Mauritius 2017 Copy Markdown View Markdown GitHub Markdown Source aOS Mauritius 2017 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aos-2017'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aos-2017'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'aOS Mauritius 2017') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-10-30 - Filed under CommunityActivity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The aOS Community - aOS stands for Azure, Office 365 and SharePoint - organised their first full-day event in Mauritius. A team of international speakers came to talk about the advantages of an integrated platform using Microsoft's platform. The event was organised with local partners Aerow ECM World Ltd. and LinkByNet Indian Ocean. Among the international speakers were a few Microsoft MVPs, namely Patrick Guimonet (@patricg), Michael Noel (@michaeltnoel), Sebastian Paulet (@SP_twit), Prabhjot Bakshi (@prabhjotbakshi), Tracy Van der Schyff (@tracyvds), and of course Chervine Bhiwoo (@chervinebhiwoo). It was really good to meet and exchange with these folks. More on that later... A different venue than usual The Labourdonnais Waterfront Hotel isn't the usual destination where we IT guys in Mauritius go to. Driving up to Port Louis in the morning was less painful than anticipated and despite having had a bit of traffic I arrived in time and was among the first attendees actually. Reaching first floor I immediately ran into Patrick. And although we exchanged on Twitter and Facebook only until this very day, it felt like a reunion of longtime friends that haven't seen each other for ages. Guess, that's what online community spirit is about: Meeting offline and in person. There was a nice breakfast arrangement and while sipping a glass of freshly-squeezed orange juice, Patrick introduced me to fellow MVPs Michael and Sebastian. Next, I met with Shyam who was on the startup team which I mentored during the MIT Global Startup Labs earlier this year. He gave me a brief update on their progress and future activities. Right now, they are looking for a tech guy who could take care of some of the implementation specific to Mauritian schools. Slowly, more attendees came and the conversations got more engaged. It was great to hear about other people's jobs, their responsibilities, and involvement in IT. After a short while Prabhjot also arrived and we had good laughs about the experience in India back in April when I spoke at the annual conference of C# Corner. Was only Chervine missing to complete the dream team. A few minutes before the opening keynote I met Vanessa and her husband Dannisen. We exchanged on the latest updates regarding our upcoming Developer Conference 2018, and then it was time to get seated. Interesting sessions The schedule already looked promising and despite the combination of sessions in English and French there was enough material advertised for me to have a great time. In classic style Patrick officially opened the conference day with his Introduction of the aOS Community. I was surprised to hear that they only started back in 2016, and have already progressed so much to travel around the world and to advocate on an international level. Well done! With the right funding everything seems to be easier. Something we have to improve within the MSCC for sure. Not the part of international activities but surely on the funding. Next, Michael went into a Detailed analysis of Office 365, and talked about all the applications and services available. Further more, he elaborated on the pricing plans and explained to the audience which features should be considered essential and worth the money to spend compared to some nice-to-have aspects. During his presentation on PowerPoint he run an add-in called Presentation Translator. It's a Microsoft Garage project that \"breaks down the language barrier by allowing users to offer live, subtitled presentations straight from PowerPoint. As you speak, the add-in powered by the Microsoft Translator live feature, allows you to display subtitles directly on your PowerPoint presentation in any one of more than 60 supported text languages. This feature can also be used for audiences who are deaf or hard of hearing.\" So, while Michael spoke in (US) English, the Presentation Translator displayed the subtitles in French. And surprisingly with acceptable accuracy. Cool, I'm going to try this myself! After that huge journey through everything Office 365 I stayed in the room to listen to Christophe's reasoning and arguments for a Digital Transformation in today's companies and how they could benefit from services offered in Office 365. Luckily, it was an easy decision for me as I already knew about Chervine's session on building an intelligent bot from previous occasions. Then it was time to move to the other room. Here, Eric Mansa from LinkByNet Indian Ocean took on the necessity of Azure Backup. He gave us a solid overview of what are the available options in Azure Backup, how recovery is done, and what kind of pricing to expect. Overall a great session in French and I could follow his explanations. Surprise! The session about the Impact of Digital Literacy on Office 365 user adaption was completely remote using Skype. Despite the weird, read: unusual, scenario to sit in a room half full of attendees but without a speaker and still listening to the shared screen on Skype together, Tracy made it an absolutely enjoyable experience. Given her experience in training of computer users she gave a good number of arguments and solutions on how an improved digital literacy would almost automatically lead to an improved user adaption among Office 365 users. During the breaks I enjoyed the chats with other attendees. And the conversation at lunch was kind of amusing, too. From own experience I can tell that the first session after lunch break is a tough one. Attendees are busy with food digestion and ready for a nap instead of paying attention to the talk. Nonetheless, Patrick went easy on us and shared his experience and knowledge about Using Office 365 in real-life examples. He talked about best practices and which pitfalls to avoid. Although, all information was surely relevant I had the impression that a large chunk of his talk was a repetition of some of the morning sessions. Next, local fellows Preetee and Kevin of Aerow gave a brief overview about the different levels of SharePoint branding and customisation. Depending on your customers needs, budget and technical understanding they came forward with three types of branding. For each level they gave good examples on how it might look like and gave their opinion on best practices. Stick to the default and don't go too fancy with your SharePoint customisation. Last but not least, Prabhjot took the stage and shared his use cases of Building intelligent apps with Internet of Things devices and how the generated data could be fed into Machine Learning and Cognitive Services in order to provide better analysis and prediction models. I've seen this talk already during the visit at the GLA University in Mathura back in April but it's still impressive to see how companies are shifting their business model. Meeting with other Microsoft MVPs I have to admit that just the appearance of other Microsoft MVPs in Mauritius was already enough for me to decide to attend the aOS Community event. Given that our island is a bit remote it was a no-brainer to use this opportunity to meet with them. Left to right: Patrick, Jochen, Prabhjot, Michael and Sebastien Chervine left right after lunch break. He still had to get his stuff ready for the upcoming aOS La Reunion meeting. Unfortunately because of that he isn't in the shot above. Surely, we have to repeat that for next year. Would be great to welcome Tracy then as well. What other aOS attendees are saying... The technical topics of the aOS Mauritius sessions were quite specific. There is interest in or maybe better said curiousity towards Azure, Office 365 and SharePoint among IT geeks in Mauritius. Close to the end, we got all together for a nice photo shooting. Smile everyone! With more IT activities like this happening these days there is also a growing number of blog articles being published on the interweb. If you're interested to know more about the first aOS Mauritius event from others, please check out the following blogs: aOS Mauritius meeting by Varun aOS Maurice 24 Octobre 2017 by Nythiennzo I'd be happy to receive some feedback from you down in the comments. Are you an active Azure, Office 365 or SharePoint user or maybe even a developer in that area? Did you attend and why? Why didn't you come to attend this unique opportunity? Resources Thankfully, the aOS Community made their presentations / slide decks available online. Check out their web site on aos.community and the various decks on SlideShare. Following is a collection from the sessions I attended: 2017 10-24 aOS présentation - aOS Mauritius SharePoint et la Gestion électronique de documents - Sébastien Paulet Transformation digitale de l’entreprise avec Office 365 - Christophe Coupez Digital Literacy and impact on Office 365 user adaption - Tracy Van der Schyff Bonnes pratiques pour votre espace de travail numérique Office 365 - Patrick Guimonet Thanks to Patrick, Patricia and aOS team for organising! Kudos to Aerow ECM World and LinkByNet for supporting! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'aos-2017.html'; this.page.identifier = '9271ed1c97_idaos-2017'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "apache-reverse-proxy.html": {
    "href": "apache-reverse-proxy.html",
    "title": "Using Apache HTTP as reverse proxy - Get Blogged by JoKi",
    "summary": "Using Apache HTTP as reverse proxy Copy Markdown View Markdown GitHub Markdown Source Using Apache HTTP as reverse proxy Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'apache-reverse-proxy'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'apache-reverse-proxy'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Using Apache HTTP as reverse proxy') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-01-10 - Filed under LinuxDevelopment (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The Apache HTTP Server, colloquially called Apache, is a free and open-source cross-platform web server. This article explains briefly how to set up Apache as a reverse proxy to a web site in an internal network. To set the expectations in this article. I'm not going to explain you how to install Apache web server or how to get it operational on your system. There are thousands of tutorials including my own Accessing your web server via IPv6 on the Internet that already cover that step. In case more information about the configuration directives used below is needed, I recommend to consult the official documentation of a particular keyword. The scenario I have a web site running on a system in an internal network. This could be either a full-fledged Windows/Linux server or an IoT device running on a single board computer (SBC), like i.e. a Raspberry Pi, an Arduino, ESP8266 chipset. Now, I want to enable access from the Internet to that internal server using Apache. Configuring Apache as reverse proxy In order to complete our task we need to look into the features of the mod_proxy module for Apache. Here, we get a directive called ProxyPass which does the job as expected. According to Apache's Reverse Proxy Guide the simplest example proxies all requests (\"/\") to a single backend: ProxyPass \"/\" \"http://www.example.com/\" Additionally, to hide any reference to the system on the internal network it is required to specify the directive ProxyPassReverse to modify certain HTTP header values in the response, and use the proxy data instead. Following is a working example of how to set up a virtual host in Apache that provides reverse proxy capabilities. <VirtualHost *:80> ServerName mediacentre.kirstaetter.name ProxyRequests On ProxyPreserveHost On ProxyVia full <Proxy *> Order deny,allow Allow from all </Proxy> ProxyPass / http://10.0.240.4:8080/ ProxyPassReverse / http://10.0.240.4:8080/ </VirtualHost> The host system on IP address 10.0.240.4 is part of an OpenVPN infrastructure and therefore accessible from the proxy system. Multiple proxies possible No problem with Apache. You can configure and run as many reverse proxies as would like to. One has to pay attention to avoid overlaps either via ServerName directive or by using different port numbers to bind to. Although I have only one reverse proxy running on Apache I configured multiple scenarios using nginx. More details are described in Using nginx as reverse proxy. Do you have any interesting use cases or active configurations of Apache as reverse proxy? If yes, please use the comment section below give me and other readers more details. Thanks! Image credit: Nick Fewing if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'apache-reverse-proxy.html'; this.page.identifier = '9271ed1c97_idapache-reverse-proxy'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "april--april---nachlese-zum-vfp-stammtisch.html": {
    "href": "april--april---nachlese-zum-vfp-stammtisch.html",
    "title": "April, April - Nachlese zum VFP Stammtisch - Get Blogged by JoKi",
    "summary": "April, April - Nachlese zum VFP Stammtisch Copy Markdown View Markdown GitHub Markdown Source April, April - Nachlese zum VFP Stammtisch Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'april--april---nachlese-zum-vfp-stammtisch'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'april--april---nachlese-zum-vfp-stammtisch'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'April, April - Nachlese zum VFP Stammtisch') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-19 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich glaube, dass sich heute (und die letzten beiden Tage) ein paar Stammtischteilnehmer vielleicht wie am 1. April vorkamen. Tut mir leid, alles meine Schuld, ich hab's verbockt. Laut der Website zum Stammtisch - https://speyer.dfpug.de - und meiner Erinnerungsmail sollte sich der Abend mit dem Internet Explorer in VFP-Anwendungen beschäftigen. Nun, sollte... irgendwie habe ich es verpeilt, dass Jörg bereits im März mir zur Kenntnis gab, dass er diese Woche in Urlaub ist, und daher nicht zur Verfügung steht. Gut, alternativ hätte man dann Automation von Microsoft Winword für Druckausgaben anstreben können. Aber Moment mal, da wäre Jörg ebenfalls als Redner aktiv gewesen. Herrje, wieder nichts, okay, dann eben Plan B und das zweite Thema vom März - Dynamic-Link Libraries in VFP verwenden - nachholen. Ihr könnt euch vielleicht vorstellen, dass wir hiermit den zweiten Satz der Thermodynamik ziemlich gut erfüllt haben. Nun, zumindest stand der Termin; wie bisher immer am 3. Mittwoch des Monats. Pünktlich um 19:30 durften wir uns im frisch renovierten (großen) Nebenraum unseres Lokals versammeln und loslegen. Zunächst wie üblich die Neuigkeiten aus der Community: Usergroupunterstützung durch O'Reilly und MS Press Infos von CLIP und der INETA Ausblick auf die Regionalleitertreffen von PASS und dFPUG Ein paar Nachzügler trafen noch ein und dann konnten wir auch direkt mit dem Thema des heutigen Abends loslegen: Dynamic-Link Libraries (DLLs). Laut MSDN sind DLLs 'lediglich' Module mit ausgelagerten Funktionen und Daten, welche von anderen DLLs oder Anwendungen genutzt werden können. Klingt im ersten Moment sehr stark nach Prozedurenbibliotheken und APP-Projekten in Visual FoxPro, und im zweiten Augenblick hat sich auch kaum etwas geändert. Einzig der Umstand, dass DLLs in zwei verschiedenen Ausprägungen vorliegen können. Ich beleuchte Dynamic-Link Libraries hier nur im Zusammenspiel mit den Eigenheiten von VFP und nicht allgemein für andere Programmiersprachen. In VFP können DLLs über zwei unterschiedliche Wege genutzt werden: Declare Befehl CreateObject(), NewObject() oder GetObject() Instanzierung über COM Declare Über den Declare-Befehl können wir direkt auf die exportierten Funktionen einer Bibliothek zugreifen und diese in den eigenen Speicher laden. Das Ganze passiert dann In-Process und die deklarierten Funktionen stehen dem Entwickler wie normale VFP-Funktionen zur Verfügung. Man kann sich mittels des Befehls DISPLAY DLLS über die aktuell zur Verfügung stehenden Funktionen informieren. Hier mal ein simples Beispiel: Declare Integer Sleep In kernel32.dll ; Integer Sleep(4000) && 4 Sekunden warten Clear Dlls \"Sleep\" Wir laden uns die exportierte Funktion Sleep aus der kernel32.dll in unsere Anwendung, führen sie dann mit einem Parameter (gemäß Deklaration) aus, und entfernen sie abschließend wieder. Das gleiche Verhalten wie beim Aufruf einer Funktion in einer VFP-Prozedurendatei, welche mittels Set Procedure To referenziert wurde. Der Verweis auf die DLL erfolgt normalerweise ohne Pfadangaben, kann aber auch Pfadinformationen nutzen. Ohne Pfad sucht VFP die DLL an den üblichen Stellen im Betriebssystem und verweigert erst die Ausführung, falls wirklich nichts zu finden ist. Das kann potentiell zu Problemen führen, falls unterschiedliche Versionen der gleichen DLL vorliegen. Hier gilt es dann mittels DISPLAY DLLS zu prüfen, welche physikalische Datei herangezogen wurde. COM-Schnittstelle Die zweite Art der Nutzung von Dynamic-Link Libraries in VFP kann über die COM-Schnittstelle erfolgen. Hier gelten dann de facto die gleichen Regeln wie der OLE Automation. Objektreferenz auf die Klassendefinition erzeugen, Methoden mit passenden Parameter ausführen und die Rückgabewerte interpretieren. Im letzten Beitrag .NET per COM nutzen habe ich genau diesen Fall bereits ausführlicher beschrieben. Hilfsmittel erleichtern die Arbeit Oftmals steht man am Anfang vor dem Problem überhaupt Informationen zur vorliegenden DLL zu erhalten. Denn entweder kennt man die Funktionen einer Bibliothek überhaupt nicht, oder es liegen zu wenige Informationen oder Dokumentation vor. Meine gegenwärtige Vorgehensweise ist die, dass ich zuerst die DLL im Dependency Walker öffne und nachschaue, ob exportierte Funktionen vorliegen. Falls ja, okay, dann kann ich mit Declare arbeiten; falls nein, dann weiß ich, dass ich die DLL registrieren muss und über die COM-Schnittstelle zu gehen habe. Danach nutze ich die Fähigkeiten des Object Browser in VFP und schaue, welche Klassen samt Methoden, Eigenschaften und Ereignissen (und Interfaces) zur Verfügung stehen. Sollten die ersten Tests mittels Createobject im Command Window fehlschlagen, gint's einen kurzen Exkurs mit Suchabfrage in der Windows-Registry, um die ProgID des COM-Servers zu ermitteln. Danach lässt sich in den meisten Fällen stressfrei mit der DLL in VFP arbeiten. Parametertypen und Strukturen Im Umgang mit den Funktionen bzw. Methoden einer Dynamic-Link Library wird man öfters (eigentlich immer) vor das Problem gestellt, dass man bestimmte Typen für die Parameter anzugeben hat. Die üblichen Vertreter sind hierbei Handle (Integer), LptStr (String) und dann die fieseren Strukturen. In der Onlinehilfe von VFP findet man eine kleine Übersetzungstabelle für die üblichen C/C++ Datentypen auf die gewohnten VFP-Typen. Diese solltet ihr euch ausdrucken und in der Nähe des Computers zurecht legen. Bei Strukturen hilft meistens nur der Taschenrechner. Denn Strukturen werden in VFP als String mit der entsprechenden Summe der Länge der genutzten Datentypen genutzt. Alles klar... oder? In den Solution Samples gibt es dazu übrigens ein Beispiel, ich möchte hier auch kurz eins zeigen: #DEFINE TIME_ZONE_SIZE 172 #DEFINE TIME_ZONE_ID_UNKNOWN 0 #DEFINE TIME_ZONE_ID_STANDARD 1 #DEFINE TIME_ZONE_ID_DAYLIGHT 2 *#beautify keyword_nochange * see [https://support.microsoft.com/kb/894818/](https://support.microsoft.com/kb/894818/) DECLARE INTEGER GetTimeZoneInformation IN kernel32.dll; STRING @ lpTimeZoneInformation *#beautify *| typedef struct _TIME_ZONE_INFORMATION { *| LONG Bias; 0 4 *| WCHAR StandardName[ 32 ]; 4 64 *| SYSTEMTIME StandardDate; 68 16 *| LONG StandardBias; 84 4 *| WCHAR DaylightName[ 32 ]; 88 64 *| SYSTEMTIME DaylightDate; 152 16 *| LONG DaylightBias; 168 4 *| } TIME_ZONE_INFORMATION, *PTIME_ZONE_INFORMATION; total 172 bytes LOCAL cBuffer, nResult cBuffer = Repli(Chr(0), TIME_ZONE_SIZE) nResult = GetTimeZoneInformation(@cBuffer) DO CASE CASE nResult = TIME_ZONE_ID_STANDARD ? \"StandardDate member\" CASE nResult = TIME_ZONE_ID_DAYLIGHT ? \"DaylightDate member\" CASE nResult = TIME_ZONE_ID_UNKNOWN ? \"The system cannot determine the current time zone\" OTHER ? \"The Time Zone ID is invalid\" ENDCASE ? ? \"Bias (minutes between UTC and local):\", buf2dword(SUBSTR(cBuffer, 1,4)) ? \"Standard Bias:\", buf2dword(SUBSTR(cBuffer, 85,4)) ? \"Daylight Bias:\", buf2dword(SUBSTR(cBuffer, 169,4)) ? ? \"Standard name:\", STRTRAN(SUBSTR(cBuffer, 5,64), Chr(0),\"\") ? \"Daylight name:\", STRTRAN(SUBSTR(cBuffer, 89,64), Chr(0),\"\") ? ? \"Standard Date:\",; buf2word(SUBSTR(cBuffer,69,2)),; buf2word(SUBSTR(cBuffer,71,2)),; buf2word(SUBSTR(cBuffer,73,2)),; buf2word(SUBSTR(cBuffer,75,2)),; buf2word(SUBSTR(cBuffer,77,2)),; buf2word(SUBSTR(cBuffer,79,2)) ? \"Daylight Date:\",; buf2word(SUBSTR(cBuffer,153,2)),; buf2word(SUBSTR(cBuffer,155,2)),; buf2word(SUBSTR(cBuffer,157,2)),; buf2word(SUBSTR(cBuffer,159,2)),; buf2word(SUBSTR(cBuffer,161,2)),; buf2word(SUBSTR(cBuffer,163,2)) FUNCTION buf2dword(lcBuffer) RETURN Asc(SUBSTR(lcBuffer, 1,1)) + ; BitLShift(Asc(SUBSTR(lcBuffer, 2,1)), 8) +; BitLShift(Asc(SUBSTR(lcBuffer, 3,1)), 16) +; BitLShift(Asc(SUBSTR(lcBuffer, 4,1)), 24) FUNCTION buf2word(lcBuffer) RETURN Asc(SUBSTR(lcBuffer, 1,1)) + ; Asc(SUBSTR(lcBuffer, 2,1)) * 256 Die Funktion GetTimeZoneInformation setzt sich aus unterschiedlichen Datentypen zu einer Gesamtgröße von 172 Bytes zusammen. In VFP erstellen wir daher eine Zeichenkette aus Chr(0) dieser Länge und übergeben den Wert per Referenz (@-Parameter), um als Rückgabewert die gewünschten Informationen zu erhalten. Wir müssen daher beim Zerlegen des Strings berücksichtigen, dass die Bytes entsprechend zu überführen sind. Was tun bei Fehlern? Zuerst werfe ich bei Fehlern im Zusammenhang mit DLLs je nach Typ entweder den Dependency Walker oder den VFP Object Browser an, um mich zu vergewissern, dass meine Funktionsaufrufe die richtigen Parametertypen verwenden. Danach nehme ich den FileMon von Sysinternals um zu prüfen, ob eventuell Pfadangaben fehlerhaft sind oder ob benötigte Dateien nicht gefunden werden. Hierbei ist ein bisschen Detektivarbeit gefordert, aber man hat sehr hohe Trefferquoten zur Behebung der Fehlerursache. Es soll auch ab und zu hilfreich sein, mal in die Dokumentation zur DLL reinzuschauen (sofern vorhanden). Und falls die DLL von einem Drittanbieter stammt, kann man immer noch eine Mail versenden und einfach mal fragen. Weitere Informationen und Beispiele Zunächst sollte man in VFP die Informationen zum Declare-Befehl und den referenzierten Befehlen und Artikel durchlesen. Danach bietet sich auf alle Fälle die MSDN Bibliothek an, da in den meisten Fällen mit einer der hunderten Funktionen der Windows API gearbeitet wird. Und bei Problemfällen mal die genannten Tools probieren. Hier die Links in der Übersicht: https://msdn.com bzw. https://msdn.microsoft.com https://www.dependencywalker.com https://www.sysinternals.com https://jochen.kirstaetter.name/files/joki_ApiSamples.zip (Slide und Code) /NET-per-COM-nutzen /Spass-mit-der-MessageBox Der nächste Termin für den Stammtisch ist am 17. Mai 2006 und das Thema wird wahrscheinlich nicht AFP sein, sondern wird mittelfristig per Mail und Forum noch bekannt gegeben werden. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'april--april---nachlese-zum-vfp-stammtisch.html'; this.page.identifier = '9271ed1c97_idapril--april---nachlese-zum-vfp-stammtisch'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "arbeiten-mit-dem-visual-studio-2005-team-system.html": {
    "href": "arbeiten-mit-dem-visual-studio-2005-team-system.html",
    "title": "Arbeiten mit dem Visual Studio 2005 Team System - Get Blogged by JoKi",
    "summary": "Arbeiten mit dem Visual Studio 2005 Team System Copy Markdown View Markdown GitHub Markdown Source Arbeiten mit dem Visual Studio 2005 Team System Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'arbeiten-mit-dem-visual-studio-2005-team-system'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'arbeiten-mit-dem-visual-studio-2005-team-system'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Arbeiten mit dem Visual Studio 2005 Team System') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-02 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Umfassender Überblick und Ausblick auf Möglichkeiten \"Ein Überblick über die Teamentwicklungsumgebung für Entwickler, Tester, Architekten und Projektmanager\" - dieser Untertitel trifft den Nagel exakt auf den Kopf. Ich habe bisher selten ein Fachbuch gelesen, welches diese klare Strukturierung in den Kapiteln und verständliche Formulierung aufweist, wie das Werk von Richard Hundhausen. Die Aufteilung der Kapitel nach Einführung, den einzelnen Projektrollen und der Erweiterbarkeit von Microsoft Team System ist optimal. Das Buch ist gleichermaßen für Einsteiger und Fortgeschrittene geeignet. Insbesondere die Schilderungen zu den beiden mitgelieferten Methodologien des Microsoft Solutions Framework (MSF) 4.0 und die nahezu unbegrenzten Möglichkeit zur individuellen Anpassung des Team Foundation Servers sind ausreichend umfangreich und regen den Appetit auf mehr Details an. Meiner Einschätzung nach ein \"Must-Read\"-Titel für die Entscheidung für und Nutzung von Microsoft Visual Studio 2005 Team System. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'arbeiten-mit-dem-visual-studio-2005-team-system.html'; this.page.identifier = '9271ed1c97_idarbeiten-mit-dem-visual-studio-2005-team-system'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "asp-net-und-visual-foxpro.html": {
    "href": "asp-net-und-visual-foxpro.html",
    "title": "ASP.NET und Visual FoxPro - Get Blogged by JoKi",
    "summary": "ASP.NET und Visual FoxPro Copy Markdown View Markdown GitHub Markdown Source ASP.NET und Visual FoxPro Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'asp-net-und-visual-foxpro'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'asp-net-und-visual-foxpro'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ASP.NET und Visual FoxPro') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-29 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); ASP.NET, COM, OLE DB, VFP, XML, Web Services Der Vollständigkeit wegen möchte ich ebenfalls einen Eintrag zum Thema ASP.NET in Verbindung mit Visual FoxPro hier publizieren. In meinem WebCast zum Thema Visual FoxPro als Datenbank für Visual Studio 2005 weise ich bereits auf die Möglichkeiten der Verwendung von VFP im .NET Framework hin. Der WebCast beschäftigt sich mit der Anzeige und Manipulation beliebiger VFP-Tabellen über ADO.NET. Basierend auf der Personalvorstellung von Cathi Gero im Buch .NET for Visual FoxPro Developers aus dem Verlag Hentzenwerke habe ich den Hinweis gefunden, dass es einen Artikel Using ASP.NET in Visual Studio .NET with Visual FoxPro 7.0 gibt und diesen möchte ich nicht vorenthalten. Der Artikel stammt zwar aus dem Jahre 2002, ist aber garantiert noch heutzutage topaktuell. Statt VFP 7.0 wird eben VFP 9.0 samt aktuellem Ole DB Provider genutzt. Viel Spass beim Lesen und Verwenden. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'asp-net-und-visual-foxpro.html'; this.page.identifier = '9271ed1c97_idasp-net-und-visual-foxpro'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "assembling-an-ai-publishing-agency.html": {
    "href": "assembling-an-ai-publishing-agency.html",
    "title": "From Solo Blogger to Editorial Board: How I Built a Multi-Agent Publishing Agency - Get Blogged by JoKi",
    "summary": "From Solo Blogger to Editorial Board: How I Built a Multi-Agent Publishing Agency Copy Markdown View Markdown GitHub Markdown Source From Solo Blogger to Editorial Board: How I Built a Multi-Agent Publishing Agency Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'assembling-an-ai-publishing-agency'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'assembling-an-ai-publishing-agency'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'From Solo Blogger to Editorial Board: How I Built a Multi-Agent Publishing Agency') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2026-08-22 - Filed under AIProductivityToolingDevelopment (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); For more than a decade, running this blog was a purely solo operation. The rhythm was familiar: an exciting technical idea would spark during a project or a conference talk, I would quickly create a draft, give it an intro, jot down three-four bullet points and a code snippet, and promise myself I would finish it over the weekend. Then reality set in. A week later, that outline was buried under daily engineering priorities. When I finally audited posts/draft/, the inventory was sobering: fifty-six (56!) unfinished drafts. Some were rich conceptual essays that had lost momentum; others were single-paragraph stubs whose original context had faded. The bottleneck was never a lack of ideas. The bottleneck was the exhausting mental friction of playing six distinct professional roles at once: investigative researcher, technical writer, proofreader, graphic designer, SEO specialist, and DevOps build engineer. Here is the story of how, through six structured evolutionary stages, I transitioned from an overwhelmed solo blogger into the director of an eight-member virtual publishing agency powered by Antigravity workspace skills. Of course, these skills are AI harness-agnostic, residing in the .agents/ folder, and can be ported into other multi-agent systems. The Agency Roster Before diving into the evolution, meet the team that now runs the blog editing and publishing desk alongside me: The eight specialised publishing personas, balanced in gender, background, and expertise. Each persona is permanently defined in the workspace under .agents/skills/, complete with dedicated instructions, reference documentation, and avatar profiles: Chief Editor: Enforces British English, style guides, keeps the voice according to my blog style and strict fluff elimination. Critique: Challenges assumptions, highlights technical tradeoffs, and acts as a constructive devil's advocate. Technical Code Reviewer: Inspects syntax highlighting tags, sanitises HTML entities, and verifies command safety. Research Assistant: Gathers documentation, verifies social citations, and manages isolated draft workspaces. Content Designer: Crafts visual prompts, generates responsive WebP images, and writes social media teasers. SEO Specialist: Audits metadata snippet lengths, manages DocFX xref cross-links, and curates tag taxonomies. Content Strategist: Triages the draft backlog, plans editorial roadmaps, and structures thematic series. Release Manager & Site QA: Enforces zero-warning DocFX builds and guards production deployments. Don't get me wrong, that's my editorial crew. I'm still the original brain and content writer of every blog article, only empowered now based on the agents' feedback and input. Stage 1: The Solo Blogger's Bottleneck A blog is deceptively complex. Writing the prose is only about thirty percent of the total effort required to produce a high-quality technical article. The remaining seventy percent consists of: Grounding technical claims against current documentation and release notes. Verifying code snippets and terminal commands for syntax and correctness. Creating evocative hero visuals and converting them into responsive image sizes. Formatting OpenGraph metadata, descriptive search summaries, and cross-references to previous articles. Link checking and compiling the static site with zero warnings or errors. Attempting to do all of this in a single sitting leads to context-switching paralysis. I needed assistance, but generic AI chat prompts were not the answer. Asking a general-purpose model to \"write a blog post\" produced generic, sycophantic text laden with clichés, American spellings, and hallucinated quotes. I needed structured specialisation. I needed an editorial agency. Stage 2: Hiring the Editorial Core The first breakthrough came when I separated stylistic enforcement from adversarial peer review. 1. The Chief Editor I codified my blog style into a standalone AUTHORING.md guide and created the Chief Editor skill. Her mandate is simple and uncompromising: British English (B.E.): Strict adherence to -ise suffixes (categorise, prioritise, synthesise), consonant doubling (signalled, travelling), and programme for initiatives vs program for code. The Strict No-Em-Dash Rule: Em-dash characters (—) are permanently banned in favour of standard hyphens, commas, or semicolons. Fluff Elimination: Outlawing lazy filler words such as \"Let's be honest...\", \"basically\", \"easily\", and \"just\". DRY Frontmatter: Populating only the primary image: attribute while leaving redundant fallback properties empty. In Antigravity (or probably any other AI harness nowadays), every persona is codified as a workspace skill under .agents/skills/<role>/SKILL.md combining YAML metadata, avatar references, and structured operational rules: --- name: chief-editor description: >- Validates blog drafts against AUTHORING.md guidelines. Enforces British English, the no-em-dash rule, and DRY frontmatter. --- # Chief Editor > **Persona Profile**: *Senior British Female Chief Editor* > Meticulous, scholarly, and uncompromising on orthography and house style. ## Core Checklist & Responsibilities 1. **British English**: Verify `-ise` suffixes and consonant doubling. 2. **Punctuation**: Enforce the strict no-em-dash rule (use hyphens or colons). 3. **Fluff Elimination**: Reject filler words (\"basically\", \"easily\", \"just\"). 4. **Frontmatter**: Verify DRY attributes and valid publication metadata. 2. The Critique (Constructive Devil's Advocate) A great (technical) post cannot simply be an echo chamber of praise. I introduced the Critique persona to serve as an adversarial reviewer. Whenever a draft makes a bold claim (for instance, evaluating a new framework or remote tooling feature), the Critique agent actively challenges it: What are the failure modes when network connectivity drops? What is the operational overhead compared to existing CLI tools? Are there platform-specific disparities between Linux, Windows and macOS? This adversarial step ensures that every published post acknowledges real-world friction and architectural tradeoffs. I take this criticism and feedback quite seriously, investigating each point raised. Stage 3: Grounding Facts & Code Integrity With editorial discipline established, the next challenge was factual accuracy and code hygiene. Hence I set up a folder for each draft to collect information, e.g. search results, online references, images, diagrams, and so forth. \uD83D\uDCC1 posts/draft/assets/<uid>/ \uD83D\uDCC4 resources.md: Grounding citations, official URLs, prompt logs & research notes \uD83D\uDDBC️ hero-source.jpg: Raw high-res image generation master \uD83D\uDCF8 screenshot.png: Original testing captures & UI evidence This acts as my *Zettelkasten* - a dedicated storage space capturing research notes, prompts, and visual assets for each draft. In future iterations, I plan to explore knowledge graphs to extract deeper relationships across these materials, rather than letting valuable contextual research remain static. I mean, it would be a shame to have a treasure trove of information and being incapable of leveraging more out of it. 3. The Research Assistant To prevent AI hallucination, the Research Assistant is tasked with gathering authoritative sources before drafting begins as well as during the writing and review phase(s). All research notes, official changelog links, and verified social media citations are catalogued in an isolated workspace under posts/draft/assets/<uid>/resources.md. When I need to quote community discussions or announcements, the Research Assistant retrieves real post status URLs and timestamps rather than fabricating quotes. 4. The Technical Code Reviewer Code snippets require dedicated inspection. The Technical Code Reviewer examines every fenced code block to ensure: Appropriate language identifiers (e.g. bash, python, csharp, yaml, json, foxpro, vfp). Elimination of HTML entity corruption (e.g. running a script to strip out corrupted entities like &nbsp; or &lt;). Complete absence of hardcoded secrets, personal tokens, or insecure defaults. Stage 4: Art Direction, SEO & Pipeline Strategy Once the core content engine was humming, I scaled visual creation, search discoverability, and backlog triage. flowchart LR Strategist[\"\uD83D\uDDFA️ Content Strategist<br/><i>Draft Triage & Roadmaps</i>\"] --> Author[\"✍️ Authoring & Drafting\"] Author --> Designer[\"\uD83C\uDFA8 Content Designer<br/><i>WebP Assets & Socials</i>\"] Author --> SEO[\"\uD83D\uDCC8 SEO Specialist<br/><i>xref: Links & Metadata</i>\"] 5. The Content Designer The Content Designer handles visual storytelling: Formulating evocative prompts for multimodal image generation (such as 1970s retro aesthetics or modern comic lineups). Processing raw outputs into responsive WebP size buckets. Crafting companion social media teasers tailored for X, BlueSky, Mastodon, and LinkedIn with strict length of character verification. 6. The SEO & Taxonomy Specialist The SEO Specialist ensures that articles reach readers effectively: Validating metadata summary lengths (keeping meta descriptions between 120 and 160 characters). Identifying opportunities for internal linking across published posts using DocFX cross-reference syntax (e.g. referencing community speaking recaps or QR matrix workflows). Maintaining consistent tag taxonomy to prevent tag fragmentation. 7. The Content Strategist With over fifty drafts waiting to be written, the Content Strategist regularly audits the queue, sorting substantive works in progress from exploratory outlines, and prioritising articles aligned with current industry themes. Stage 5: Evaluation, Fine-Tuning & Output Calibration Creating specialised agents is only half the battle. Without systematic evaluation and continuous prompt tuning, agents gradually suffer from rule drift, over-verbose outputs, and hallucinated formatting. flowchart TD Raw[\"\uD83E\uDD16 Raw Agent Output\"] --> Linter[\"\uD83D\uDCCF Constraint Audits<br/><i>B.E. Orthography, Char Caps, DRY</i>\"] Linter --> Eval[\"\uD83E\uDDEA Adversarial Testing<br/><i>Edge Cases & Technical Tradeoffs</i>\"] Eval --> Calibration[\"\uD83C\uDFAF Multi-Model Calibration<br/><i>Nano Banana 2, Gemini Flash</i>\"] Calibration --> HumanDirector{\"\uD83D\uDC64 Editorial Director<br/><b>Variant Selection & Sign-Off</b>\"} 1. Programmatic Constraint Enforcement & Linters Rather than relying on vibes, I codified strict, quantifiable acceptance criteria for every generated asset: Social Character Caps: Social copy is strictly measured against platform limits prior to presentation (e.g. X at 280 characters including t.co shortening, BlueSky at 300 characters, Mastodon at 500 characters, and LinkedIn formatted with hook-and-bullets). Automated Entity Linters: Running standalone Python validation tools (fix-html-entities and manage-hero-assets) ensures that code blocks do not contain corrupted HTML entities (&nbsp;, &lt;) and that images strictly conform to responsive 16:9 formats. 2. Multi-Model Image Calibration (Nano Banana 2) Visual generation required continuous fine-tuning. Early image models struggled with in-image typography, rendering illegible gibberish on books and posters. By integrating modern image models like Nano Banana 2 directly into our CLI tooling, I unlocked: Crisp, legible text rendering in architectural diagrams and mockups. Consistent character styling and colour palette across sequential articles. Automatic generation of frosted-glass OpenGraph preview cards featuring QR codes. 3. Iterative Skill Calibration & Prompt Refinement Workspace skills under .agents/skills/ are treated as software dependencies. When underlying LLM architectures receive version bumps, I run prompt calibration cycles: The Divergence-Only Rule: Refined the SEO Specialist skill to ensure frontmatter attributes like metaTitle and metaDescription are only populated when intentionally diverging from the post title and summary, avoiding DRY redundancy. Multi-Variant Outreach Drafting: Instructed the Content Designer to always generate distinct strategic angles (e.g. Backlog Hook vs Productivity Insight) with exact character tallies, allowing the human author to select the winning voice. Stage 6: The Pre-Press Desk & The Deployment Guardrail The final piece of the architecture is the Release Manager & Site QA. flowchart LR S1[\"1️⃣ Pre-Actions<br/><i>Asset Localisation</i>\"] --> S2[\"2️⃣ DocFX Build<br/><i>Zero-Warning Gate</i>\"] S2 --> S3[\"3️⃣ Post-Actions<br/><i>Clean Markdown</i>\"] S3 --> S4[\"4️⃣ Local Site<br/><i>(Port 8080)</i>\"] S4 --> S5{\"5️⃣ Human Gate<br/><b>Explicit Approval</b>\"} S5 -->|Approved| S6[\"6️⃣ Deploy to<br/>Production\"] The Release Manager enforces our strict quality gates: Zero-Warning DocFX Builds: The compilation must finish with exactly 0 warning(s) and 0 error(s). Hyperlink & Asset Integrity: Checking that all image paths resolve and Service Worker caching registers cleanly. Clean Git Guardrails: Running a script to ensure all content assets are committed. The Non-Negotiable Human-in-the-Loop Rule There is one inviolable constraint in our agency charter: Important No Automated Deployments: Under no circumstances does any agent deploy without explicit, interactive human approval. The human author is the origin of the post, the provider of lived debugging experiences, and the final decision-maker. The agents serve as rigorous scaffolding, quality gates, and sounding boards - never unmonitored ghostwriters. Operational Tradeoffs & Hidden Overhead Building an agency of specialised agents sounds utopian, but engineering pragmatism demands acknowledging the real-world tradeoffs: Orchestration Latency vs Output Quality: Running eight agents sequentially on every 400-word quick tip is overkill. I adopt a tiered invocation model: lightweight tips activate only the Chief Editor and Technical Code Reviewer; full architectural essays engage the entire editorial board. Token Economics & Context Boundaries: Injecting bulky system prompts and house rules on every prompt burns tokens rapidly. Storing research in posts/draft/assets/<uid>/resources.md and using progressive skill disclosure keeps active context windows lean. Prompt Drift & Maintenance Debt: Skills in .agents/skills/ are living code. When underlying foundational models update, rule adherence (such as British English orthography or strict em-dash avoidance) requires periodic regression testing. Complete Agency Workflow Here is how all eight roles collaborate across the end-to-end publishing lifecycle: The end-to-end publishing pipeline from authoring to pre-flight QA. Key Takeaways Specialisation Beats Generalisation (With Orchestration Discipline): Modular skills produce sharper results than monolithic prompts, but require disciplined gating to avoid review fatigue. Adversarial Critique is Essential: Having a designated agent challenge your claims elevates an article from promotional hype into authoritative technical writing. Isolate Working Assets: Keeping draft research and prompt logs in isolated workspaces (posts/draft/assets/<uid>/) keeps production repositories clean. Enforce Hard Deployment Guardrails: Automate everything up to local emulation, but keep deployment strictly guarded behind explicit human approval. Join the Conversation Have you experimented with multi-agent workflows or specialised subagents for technical writing, documentation, or content authoring? What guardrails do you put in place to maintain your authentic voice? Let me know your thoughts on X (@JKirstaetter), connect on BlueSky (@jochen.kirstaetter.name), find me on Mastodon (@JKirstaetter), or subscribe to the RSS Feed. Picture credits: AI-generated visual assets produced with Gemini 3.1 Flash Image and Nano Banana, styled in modern graphic novel vector art. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'assembling-an-ai-publishing-agency.html'; this.page.identifier = '9271ed1c97_idassembling-an-ai-publishing-agency'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ate-auf-der-love-roadshow.html": {
    "href": "ate-auf-der-love-roadshow.html",
    "title": "ATE auf der LOVE Roadshow - Get Blogged by JoKi",
    "summary": "ATE auf der LOVE Roadshow Copy Markdown View Markdown GitHub Markdown Source ATE auf der LOVE Roadshow Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ate-auf-der-love-roadshow'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ate-auf-der-love-roadshow'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ATE auf der LOVE Roadshow') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-03 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); LOVE - Launch Office 2007, Windows Vista und Exchange 2007 Roadshow Cooles Akronym, muss man wirklich sagen. Gefällt mir extremst gut und drückt scheinbar die aktuelle und zukünftige Stimmungslage von Microsoft mit seinen Kunden aus - voll auf Schmusekurs? Die aktuellen Infoseiten zu Windows Vista und Office 2007 wirken auf alle Fälle weitaus freundlicher, weniger technisch überladen und übersichtlicher als in der Vergangenheit. Das farbliche Konzept spricht zumindest mich an. Dennoch meldet sich im Hinterstübchen natürlich der warnende Zeigefinger... nicht alles was glänzt, ist golden. \uD83D\uDE01 Gut, zunächst geht's nächste Woche auf die 13. Entwicklerkonferenz Visual FoxPro aka 5. SQL und .NET Konferenz der deutschsprachigen FoxPro User Group, und Anfang Dezember gleich auf zwei Termine der LOVE Roadshow. Ich bin gespannt, was mich und euch(?) erwarten wird. Bis denne, JoKi PS: Wem ist eigenlich aufgefallen, dass Microsoft zum einiger Zeit ein neues Favicon - - verwendet? if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ate-auf-der-love-roadshow.html'; this.page.identifier = '9271ed1c97_idate-auf-der-love-roadshow'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ate-fr-roadshow-office-2007-windows-vista.html": {
    "href": "ate-fr-roadshow-office-2007-windows-vista.html",
    "title": "ATE für Roadshow Office 2007, Windows Vista... - Get Blogged by JoKi",
    "summary": "ATE für Roadshow Office 2007, Windows Vista... Copy Markdown View Markdown GitHub Markdown Source ATE für Roadshow Office 2007, Windows Vista... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ate-fr-roadshow-office-2007-windows-vista'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ate-fr-roadshow-office-2007-windows-vista'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ATE für Roadshow Office 2007, Windows Vista...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-09 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); ATE steht für 'Ask The Experts'. Ursprünglich hatte ich mich nicht als ATE für die kommende Roadshow beworben und mich auch bereits für die Veranstaltung in Frankfurt angemeldet. Doch erstens kommt es anders, und zweitens als man denkt... oder so? Letztes Wochende, genauer Sonntag, hatte ich einen kleinen Plausch mit meinem Azubi Thomas über seine Bewerbung als ATE für Windows Vista, da wir ja sehr oft gemeinsam auf Achse (insbesondere bei Microsoft Events) sind. Well, im Laufe des Gespräch stellte sich heraus, dass scheinbar noch freie Plätze für Stuttgart zur Verfügung stehen und ob ich denn nicht auch Interesse hätte aktiv zu sein. Nun, für den 4.12.2006 habe ich bisher nichts im Terminplan stehen und über die Neuerungen zu Vista bzw. Office 12 lässt sich sicherlich sehr viel berichten. Bedingt durch die Entwicklung von und Programmierung mit Active FoxPro Pages und der Teilnahme am Beta-Programm zu Windows Vista liegt mein Schwerpunkt derzeit in der Handhabe der Internet Information Services (IIS) 7.0. Da ich im Laufe des Novembers einige Tage frei haben werde, nutze ich diesen Zeitraum für die intensive Vorbereitung und Vertiefung der Kenntnisse in Vista und Office 12 - jeweils primär aus der Sichtweise eines Entwicklers, und dann sekundär aus Anwendersicht... \uD83E\uDD2A Nee, Quatsch. Sowohl Vista mit dem neuen Sicherheitskonzept, IIS 7.0, PowerShell, und und und, sowie Office 12 mit der Überarbeitung der grafischen Oberfläche bieten sicherlich ausreichend Stoff zur Unterhaltung und Diskussion mit den Besuchern der Roadshow. Ich bin mal gespannt, was gehen wird. Meine Eindrücke als ATE für den Launch des SQL Server 2005 in Karlsruhe waren durchweg positiv. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ate-fr-roadshow-office-2007-windows-vista.html'; this.page.identifier = '9271ed1c97_idate-fr-roadshow-office-2007-windows-vista'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "attending-the-mauritian-bloggers-meeting.html": {
    "href": "attending-the-mauritian-bloggers-meeting.html",
    "title": "Attending the Mauritian Bloggers Meeting - Get Blogged by JoKi",
    "summary": "Attending the Mauritian Bloggers Meeting Copy Markdown View Markdown GitHub Markdown Source Attending the Mauritian Bloggers Meeting Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'attending-the-mauritian-bloggers-meeting'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'attending-the-mauritian-bloggers-meeting'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Attending the Mauritian Bloggers Meeting') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-04-21 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Through social media networks, especially Facebook, I got aware of the fact that there would be a gathering of Mauritian Bloggers. What a cool idea! Interestingly, it wasn't the first meet-up and if I'm correctly informed there had been meetings like this one since 2009. At a time, I was already on this beautiful island, and others knew that I was blogging. Well, there had been quite some rain lately, and honestly I don't want to dig up rotten corpse from the dead. Initially, I discovered the Facebook event through a comment or Like by a connected friend, as I wasn't part of the organising group 'Mauritian Bloggers' but luckily the event was open to anyone and I could follow the organisers and the comments. 2013 - Meet-up at Bagatelle Surely, a very good, central location and a wise choice by 'Carrot Madman VI'. There are surely enough parking lots, enough space to join forces and discuss one or the other topic over everyone's own preferences of food and beverages. And last but not least there are quite some locations for a decent group photo - just in case that someone 'insists' on this kind of memory hoarding. I still have to find one of the shots... Mauritian Bloggers - Help! \uD83D\uDE09 Image courtesy of Yashvin Awootar Arriving - not - in time but still not among the last Hm, the event was scheduled between 11:00 hrs and 17:00 hrs which is actually a good period of time to make it to the gathering. Personally, we managed to show up shortly before noon - due to important moves on Ingress - and there were already about ten bloggers enjoying the company. Of course, new face = short introduction... Actually, it's nice to be able to tag a face to the various blogs written about Mauritius. The atmosphere is relaxed and I was quickly involved in the ongoing conversations. Hopefully, I wasn't too overwhelming when I started to talk a bit about my latest hobby of Ingress by the Niantic Labs. Frankly speaking, I am wondering since the time I started playing Ingress about the two vital elements: the ridiculously low number of portals and the lack of players Taking into consideration that most Mauritians have Android-based smartphones, and that there are quite a number of technical blogs around I couldn't get it. But hey, I hope that I ignited something and the situation is going to change very quickly soon. \"Gonna install it as soon I reach home\" \"waiting for activation code\" \"waiting for the activation code too. I'm gonna hack your portals.\" First responses on Facebook look very promising and it would be awesome to have more action play in Ingress soon. It would be of great pleasure to write one of the first SITREPs for Mauritius. For sure, more to come. Keep an eye on my Android section here in the blog, too. Update I wrote a brief overview about playing Ingress in Mauritius. Please check it out... My conclusion This was definitely not my last attendance and I'm looking forward to the next event of Mauritian Bloggers! And a 'Big Thank You' to the organisers, too! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'attending-the-mauritian-bloggers-meeting.html'; this.page.identifier = '9271ed1c97_idattending-the-mauritian-bloggers-meeting'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "aufruf-zur-untersttzung-call-for-action.html": {
    "href": "aufruf-zur-untersttzung-call-for-action.html",
    "title": "Aufruf zur Unterstützung - Call for action - Get Blogged by JoKi",
    "summary": "Aufruf zur Unterstützung - Call for action Copy Markdown View Markdown GitHub Markdown Source Aufruf zur Unterstützung - Call for action Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aufruf-zur-untersttzung-call-for-action'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'aufruf-zur-untersttzung-call-for-action'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Aufruf zur Unterstützung - Call for action') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-19 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Bei der Recherche zu den Sessionnotes der Devcon Prag 2006 bin ich gestern im DevBlog von Craig Bernston hängengeblieben. Dort steht ein Beitrag mit einem Verweis auf FoxPro Successes. Dabei handelt es sich um eine Website zum Zusammentragen und Veröffentlichen von Erfolgsgeschichten bzw. Fallstudien zu Visual FoxPro Projekten. *The FoxProSuccess.com site is intended to be a resource for the community to let the world know what a productive community can do with a vital tool like Visual FoxPro. We hope to gather success stories from guest writers into this central repository. Success can be measured in many ways: On time and On Budget, Exceeding client expectations, sales and client retention, increases in productivity, saving a project that was DOA with other technologies, large multi-user support, integrating new or interesting technologies, etc.* Hier nun mein Aufruf an euch. Reflektiert doch mal eure eigenen Entwicklungen und Kundenprojekte. Wie sieht's bei euch aus? Ihr habt doch alle sicherlich bereits ein Projekt erarbeitet, auf das ihr sehr stolz seid, oder? Wie sieht's mit einer kurzen Beschreibung dazu aus? Was ist das Besondere an eurem Projekt? Habt ihr anderen Entwicklern euere Projekt mal gezeigt und mit Ihnen diskutiert? Falls ja, dann auf... Mal etwa ein, zwei Stunden der Muße gefröhnt und eine Beschreibung für eines eurer Projekte formuliert. Es ist einfacher als ihr denkt. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'aufruf-zur-untersttzung-call-for-action.html'; this.page.identifier = '9271ed1c97_idaufruf-zur-untersttzung-call-for-action'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ausbildung-fachinformatiker-anwendungsentwicklung.html": {
    "href": "ausbildung-fachinformatiker-anwendungsentwicklung.html",
    "title": "Ausbildung: Fachinformatiker Anwendungsentwicklung - Get Blogged by JoKi",
    "summary": "Ausbildung: Fachinformatiker Anwendungsentwicklung Copy Markdown View Markdown GitHub Markdown Source Ausbildung: Fachinformatiker Anwendungsentwicklung Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ausbildung-fachinformatiker-anwendungsentwicklung'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ausbildung-fachinformatiker-anwendungsentwicklung'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ausbildung: Fachinformatiker Anwendungsentwicklung') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-12-16 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Okay, gleich zur Klarstellung: Ich bilde aus und nicht anders... \uD83D\uDE0E Zwar zu einem ungewöhnlichen Zeitpunkt, seit dem 01.12., aber dennoch ganz klar im Zeichen der Ausbildung zum Fachinformatiker mit Schwerpunkt Anwendungsentwicklung. Wir, also die Kheops-ProLib-Firmengruppe, bilden bereits seit Jahren in der Ausbildung zum Fachinformatiker aus. Und da dieses Jahr unser bisheriger Ausbildungsplatz erneut durch den erfolgreichen Abschluß 'frei' geworden ist, haben wir die Ausbildungsstelle neu vergeben. Wurde nach anfänglichen Gesprächen inzwischen auch von allen Seiten akzeptiert und gestartet. Hm, ich müsste mal überlegen, dürfte inzwischen unser 7. Auszubildender sein... Ja, Number 7. Warum schreibe ich eigentlich diesen Eintrag? Nun, in diesem Fall läuft die Ausbildung primär durch meine Planung und Gestaltung. Selbstverständlich werde ich auf die Erfahrungen und die Praktiken meiner geschätzten Kollegen zugreifen. Allein ob der Tatsache, dass wir natürlich die gleiche Ausbildungsqualität (evtl. sogar mehr? g) beibehalten werden. Und natürlich freue ich mich auf die neuen Herausforderungen. Ich glaube, die nächsten 2,5 bis 3 Jahre werden sehr aufschlussreich, mit zahlreichen Abwechslungen gespickt und hoffentlich viel Lehr- und Lernmöglichkeiten sowohl für den Auszubildenden als auch für mich als Ausbilder bieten. Ich bin auf alle Fälle mal gespannt. Und ehrlich gesagt, passt es auch optimal in meinen persönlichen Weiterbildungsplan. Denn 2006 wird definitiv das Jahr der Prüfungen - jawohl ja - die neuen MCP-Tests für .NET 2.0 und SQL Server 2005 sind bereits inspiziert und klassifiziert worden. Denn was Eisbären können, können Delfine ebenfalls. \uD83D\uDE0E So, genug für heute getippert. Im Laufe der Ausbildung wird's weitere Einträge geben... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ausbildung-fachinformatiker-anwendungsentwicklung.html'; this.page.identifier = '9271ed1c97_idausbildung-fachinformatiker-anwendungsentwicklung'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "authentication-jitsi-meet.html": {
    "href": "authentication-jitsi-meet.html",
    "title": "Enable authentication in your instance of Jitsi Meet - Get Blogged by JoKi",
    "summary": "Enable authentication in your instance of Jitsi Meet Copy Markdown View Markdown GitHub Markdown Source Enable authentication in your instance of Jitsi Meet Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'authentication-jitsi-meet'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'authentication-jitsi-meet'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Enable authentication in your instance of Jitsi Meet') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-04-20 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); A basic installation of Jitsi Meet gets you up and running within shortest time, probably in less than 15 minutes. There are hardly any configuration changes necessary. Most important information is a fully qualified domain name (FQDN), and that's it. However such a default installation of Jitsi Meet is open. Meaning, that anyone knowing the URL of your server can create a new meeting room and start to have video conferences using your instance and probably causing additional cost. In this second article on Jitsi Meet we are going to enable authentication to avoid any misuse from public users. Please read Install Jitsi Meet on Compute Engine (GCP) in case you have not created your own instance yet. Securing your instance of Jitsi Meet requires three configuration changes plus the creation of user accounts with permission to host conference calls. Let's have a look at the architecture of Jitsi Meet to get a better understanding. It is possible to allow only authenticated users for creating new conference rooms. Whenever a new room is about to be created Jitsi Meet will prompt for user name and password. After the room is created others will still be able to join from an anonymous domain. Extend the Prosody configuration The central component of Jitsi Meet is the Prosody XMPP server which is responsible for user management among other tasks, like authentication. Open the configuration file of your domain with your preferred text editor. nano /etc/prosody/conf.d/$(hostname -f).cfg.lua Here you change the current value of authentication from anonymous to internal_hashed like so. VirtualHost \"meet.mscc.mu\" -- enabled = false -- Remove this line to enable this host -- authentication = \"anonymous\" authentication = \"internal_hashed\" ... Additionally, you add a new virtual host definition at the end of the same file. ... VirtualHost \"guest.meet.mscc.mu\" authentication = \"anonymous\" c2s_require_encryption = false Save the file to confirm the modifications**.** Note: The domain of the guest VirtualHost is internal only. It does not require any DNS record or SSL certificate. The outcome is now that the primary VirtualHost of your Jitsi instance would require any kind of authentication to create a conference meeting room whereas the VirtualHost for guests still grants access to anonymous users. Add guest domain to Jitsi Meet frontend After adding the guest domain to the XMPP server component, you need to add this VirtualHost to the configuration object in the web frontend. Open the config file with a text editor. nano /etc/jitsi/meet/$(hostname -f)-config.js Then you add the directive anonymousdomain into your hosts object. hosts: { // XMPP domain. domain: 'meet.mscc.mu', // When using authentication, domain for guest users. // anonymousdomain: 'guest.example.com', anonymousdomain: 'guest.meet.mscc.mu', ... Save and close the configuration file to confirm your modifications. As you might see in the comment in the hosts sections, it already stipulates that your instance is going to use the new domain for guest users. Change Jitsi Conference Focus Next, you have to configure the Jitsi Conference Focus (jicofo) component to allow requests from an authenticated domain only. For that you need to add the protected URL to the properties files. Open it with a text editor. nano /etc/jitsi/jicofo/sip-communicator.properties Add the key-value pair org.jitsi.jicofo.auth.URL=XMPP:<domain> at the end of the file, and save it. ... org.jitsi.jicofo.auth.URL=XMPP:meet.mscc.mu Restart all Jitsi services involved With all changes mentioned above you need to restart the services to apply all modifications. Run the following commands or restart your VM instance completely. service prosody restart service jicofo restart service jitsi-videobridge2 restart service nginx restart Check the log files Should you come across some unexpected issues always have a look at the log files first. Here is a brief overview of where to check. # Prosody tail -f /var/log/prosody/prosody.log # Jicofo tail -f /var/log/jitsi/jicofo.log # Jitsi video bridge tail -f /var/log/jitsi/jvb.log # nginx tail -f /var/log/nginx/error.log Create your moderators For the last step, now that authentication is active, you need to create at least one user which is going to have permissions to create meeting rooms. According to the architecture it is the Prosody component which is responsible for this part. The command prosodyctl helps you to manage your XMPP server and therefore your user base. You can add and enable a user with the following command. prosodyctl register joki $(hostname -f) VerySecretPassword The syntax is described here. prosodyctl register --help Usage: /usr/bin/prosodyctl register USER HOST [PASSWORD] Register a user on the server, with the given password Unfortunately, this is not GDPR-compliant, because “enabling users to set their password without the admin knowing it” is a basic and unavoidable security measure. Congratulations! You completed all necessary steps to enable authentication in your instance of Jitsi Meet. All steps described above are mainly based on the official guide to Secure domain on GitHub. Let's try it... Authenticate against your instance of Jitsi Meet Open a browser and navigate to your URL of Jitsi Meet. The site should load as before and there are no obvious changes visible. Authentication is bound to the creation of a meeting room only. Either you choose an existing meeting room or you enter a new name and click on GO to start the video conference session. If you are not authenticated the site will now place you in some kind of virtual lobby until a moderator or host arrives. In case that you are the host of the meeting click on I am the host and you will be asked to enter your credentials. You can either enter just the user name without your domain or the fully qualified user name including the domain - both approaches will work. Enter your passphrase and click OK. With successful authentication against Prosody the Jitsi Meet component will grant you access to the meeting room and assign moderator permissions to your account. Public access is still possible as soon as a moderator (host) is present in the meeting room. As a moderator you will get additional options under Settings > More which allow you to control what should happen when someone enters the meeting room, e.g. being automatically muted or not being visible to other participants. Enjoy your next, secure video conference. Consider to set password per meeting room An additional level of protection against \"Zoom-bombing\" or unwanted intrusion into a video conference would be to activate the password of the meeting room. Click on the i circle in the bottom right area and click on Add password in the popup dialog. Enter your room-specific password and hit Enter to confirm your choice. Note: The password of a meeting room is not persistent and needs to set each time that you would join / start a conference call. You cannot launch a meeting room with an initial password already set. What's Next? Perhaps you noticed that the visual appearance of the Jitsi Meet instance running for MSCC looks slightly different to the default installation. Continue reading the next part of this series: Customise Your Instance of Jitsi Meet, where we explore core feature configuration, visual branding, Google Calendar synchronisation, resilient Nginx upgrade persistence, and client endpoint setup across desktop and mobile devices. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'authentication-jitsi-meet.html'; this.page.identifier = '9271ed1c97_idauthentication-jitsi-meet'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "author/joki.html": {
    "href": "author/joki.html",
    "title": "Jochen Kirstätter - Get Blogged by JoKi",
    "summary": "Jochen Kirstätter The only frontiers are in your mind Mauritius https://jochen.kirstaetter.name/ @jkirstaetter Facebook Articles Gemma 3 - missing features despite announcement - 2025-03-12 Gemini ResponseSchema vs JSON Schema specs of \"type\" as array - 2025-01-20 C# Corner Annual Conference 2017 - 2024-05-30 Untrack files in .gitignore - 2024-04-05 Quickstart: Use Gemini with .NET (Vertex AI) - 2024-04-03 Quickstart: Use Gemini with .NET (Google AI) - 2024-04-02 NuGet: Better release notes and versioning - 2024-03-31 Automate NuGet package creation using Google Build - 2024-03-04 Create NuGet packages with Visual Studio / Visual Studio Code - 2024-03-01 Getting Started with the Vertex AI Gemini API with Visual Studio - 2024-02-23 Getting started with Gemini using Visual Studio - 2024-02-22 Developer Creators and Online Communities Summit - 2024-02-01 DevFest Jinja 2023 - 2023-12-28 Mau Pas Konpran!? Brain Drain - 2023-12-19 DevFest Dar es Salaam 2023 - 2023-12-12 DevFest Mauritius 2023 - 2023-12-07 Mau pas konpran!? SIM Registration - 2023-11-19 DevFest Kigali 2023 - 2023-11-15 SSA Community Summit 2023 - 2023-11-15 Generating QR codes - the Easy, the Comfy and the Smarty - 2023-11-06 Sapiens: A Brief History of Humankind - 2023-09-26 Using OpenVPN connections via Windows Task Scheduler - 2023-06-28 GDE SSA Summit 2022 - 2022-09-27 Installing SQL Server 2019 and tools on Ubuntu 20.04 - 2022-03-25 GADS 2020 - Spotlight - 2020-10-06 How did we run the Virtual Developers Conference 2020? - 2020-09-16 Noise Cancelling on Windows 10 - 2020-05-21 Azure DEV Meetup Munich - 2020-04-24 Enable authentication in your instance of Jitsi Meet - 2020-04-20 Install Jitsi Meet on Compute Engine (GCP) - 2020-04-16 Why we decided to postpone Developers Conference 2020 - 2020-03-17 Getting Started with SQL Server on GCP - 2020-01-18 Working with Azure Container Registry - 2020-01-15 Using Cloud Run instead of GKE - 2019-11-26 Working with Cloud Build - 2019-11-23 Considerations for production readiness (ALC 4.0 Cloud Challenge I) - 2019-11-09 Google Kubernetes Engine (ALC 4.0 Cloud Challenge I) - 2019-11-09 Working with Docker (ALC 4.0 Cloud Challenge I) - 2019-10-24 Create React App (ALC 4.0 Cloud Challenge I) - 2019-10-20 ALC 4.0 Cloud Challenge I - 2019-10-18 Guest lecturer for the Africa Industrial Internet Programme (AIIP) - 2019-09-06 ALC 4.0 Cloud - Access to Qwiklabs - 2019-09-04 ALC 4.0 Cloud - Phase II Online Meet up - 2019-09-02 Share your journey - #150DaysOfALC4 - 2019-08-23 ALC 4.0 Cloud - Slack questions - 2019-08-10 Welcome - A new star is born! - 2019-03-01 Rock Your Code: Defensive Programming for Microsoft .NET by David McCarter - 2019-02-03 Welcome - Our twin stars! - 2019-02-01 Tip: Open PowerShell window here - 2019-01-11 Using Apache HTTP as reverse proxy - 2019-01-10 Using nginx as reverse proxy - 2019-01-08 First steps using dictation in Windows 10 - 2019-01-07 2018: A retrospective - 2019-01-03 Campsite fire talk with John O'Nolan - 2018-11-13 DIY Windows Game Box (Steam, Itch, Xbox, Store, Scumm, etc.) - 2018-10-17 The Curse of the Magical Tutorial - 2018-10-05 SSA Community Summit 2018 - 2018-10-04 When NULL isn't NULL - 2018-09-10 OpenVPN: All TAP-Windows adapters on this system are currently in use - 2018-08-31 OpenVPN re-visited - 2018-08-28 How to get your computer from blank disk(s) to fully operational in no time... (Windows edition) - 2018-07-28 100 Days of Exam - 2018-03-13 Next stop: MCSA: Linux on Azure - 2018-03-13 Dinner with Andy Volk - 2018-03-08 ICT skills at primary school - 2018-02-26 Infotech/InnovTech 2017 - 2018-02-03 Build anything with Google: Google Maps APIs - 2018-01-31 Hit Refresh - 2018-01-02 aOS Mauritius 2017 - 2017-10-30 Krakathon 2017 - 2017-10-27 Inspecting internet router in Mauritius - 2017-10-18 InterCommunity 2017 - 2017-10-12 Dear Postmaster of Mauritius Revenue Authority - 2017-10-03 Career Guidance (Day) at Aberystwyth University - 2017-09-26 Integrate Google AdSense into your Ghost - 2017-09-25 Tech Mentor at MIT Global Startup Lab in Mauritius - 2017-08-16 I am Iron Man! - 2017-08-13 Ghost Desktop on Xubuntu 17.04 won't start - 2017-08-08 Migration to Ghost - 2017-08-07 Learn to Code in Swift: The new language of iOS Apps by Kevin McNeish - 2016-02-28 Mauritius Open Data Readiness Assessment (ODRA) - 2015-11-07 Working as an IT freelancer in Mauritius - 2015-08-17 Launch Event: Windows 10 and Visual Studio 2015 - 2015-08-06 Installing Visual Studio Code on Linux (Ubuntu) - 2015-07-24 Ubuntu Jam at the University of Mauritius - 2015-07-18 Live and let live - modern laziness... - 2015-06-28 Upgrade to Xubuntu 15.04 - Vivid Vervet - 2015-05-09 Mugg & Bean - Nightmare at Bagatelle - 2015-05-04 Introduction to Mauritius Software Craftsmanship Community - 2015-03-01 Preparations for Developers Conference 2015 - 2015-02-20 Children are happy using their Kindle Fire HDX - 2015-01-04 My first month using a Lumia 730 - 2015-01-03 Lets rock with MySQL and MariaDB - 2015-01-02 Ten years of blogging - 400+ articles written - 2014-12-13 Good to know: Sender Policy Framework - 2014-12-12 High Performance Responsive Design by Tom Barker - 2014-11-16 Microsoft SharePoint 2013 Administration Inside Out - 2014-11-13 Social eCommerce by Stephan Spencer, Jimmy Harding & Jennifer Sheahan - 2014-11-13 Review: Emtel Wingle a.k.a. Huawei E8231 - 2014-11-05 MSCC: Shellshock Survival Guide - 2014-10-27 How to avoid accidental app purchase - 2014-09-02 MSCC: Entrepreneurship and start-up culture (in Mauritius) - 2014-08-31 Already 3,333 tweets... - 2014-08-20 Game Review: Monument Valley - 2014-08-16 Vacations on Rodrigues 2014 - 2014-08-14 Visiting the Emtel Data Centre - 2014-08-08 SUPINFO International University in Mauritius - 2014-07-14 MSCC: Purpose and benefits of Version Control Systems (VCS) - 2014-06-29 Emtel Knowledge Series - Q2/2014 - 2014-06-20 MSCC: Purpose and benefits of Version Control Systems (VCS) - 2014-06-18 MSCC: Scripting - Administrator's­ toolbox of magic... - 2014-05-04 MSCC: Career & IT Fair 2014 - 2014-04-26 Corsair Hackers Reboot - 2014-04-21 Game Review: God of Light - 2014-04-08 MSCC: Global Windows Azure Bootcamp - 2014-04-02 MSCC: Global Windows Azure Bootcamp - 29th March 2014 - 2014-03-22 Accessing your web server via IPv6 - 2014-03-16 Enabling DNS for IPv6 infrastructure - 2014-03-12 DHCPv6: Provide IPv6 information in your local network - 2014-03-11 Configure IPv6 on your Linux system (Ubuntu) - 2014-03-10 App Review: Mauritius Vibrations - 2014-03-05 How to extend a virtual disk and its logical volume(s) in CentOS - 2014-03-04 MSCC: WordPress - An Introduction - 2014-02-28 Emtel Knowledge Series - Q1/2014 - 2014-02-16 MSCC: HTML5, CSS3 and JavaScript - 2014-01-25 AngularJS by Brad Green and Shyam Seshadri - Some lack of quality - 2014-01-18 Game Review: Quell - 2014-01-10 The Superlative Stream by Kerry Nietz - 2014-01-07 A Star Curiously Singing by Kerry Nietz - 2014-01-04 Goodbye 2013 - Welcome 2014 - 2014-01-02 How to build your own 'Emtel In-Car WiFi' hot-spot - 2013-12-20 MSCC: Christmas raffle and plans for 2014 - 2013-12-17 Running Windows applications on Linux - 2013-11-24 How's Mauritius Software Craftsmanship Community going? - 2013-11-24 Infotech 2013 - Confusing and mixed impressions - 2013-11-22 Virtual Launch of Visual Studio 2013 Dubai - 2013-11-20 MSCC: Clean Code Development & Your workspace - 2013-11-18 Representing Mauritius in the 2013 Bench Games - 2013-10-29 MSCC: Clean Code Development & Flexible work environment - 2013-10-28 Upgrade to Xubuntu 13.10 - Saucy Salamander - 2013-10-24 Using a portable USB monitor in Ubuntu 13.04 (AOC e1649Fwu - DisplayLink) - 2013-10-15 Not attending the LUGM mini-meetup - 05. Oct 2013 - 2013-10-06 Microsoft and innovation: IIF() method - 2013-10-06 Resume on 30 Days of SharePoint - 2013-09-30 Update: Super Hero - 2013-09-26 MSCC: Clean Code Developer & Certification in IT - 2013-09-24 Visiting the Fire Station in Coromandel - 2013-09-09 Exploring various SharePoint blogs - 2013-09-05 SharePoint 2013 Developer Ramp-Up - Part 2 - 2013-09-03 SharePoint 2013 Developer Ramp-Up - Part 1 - 2013-09-02 Tuning SQL Server 2012 for SharePoint 2013 Jump Start - 2013-09-01 The 30 days of Microsoft SharePoint challenge - 2013-08-31 Is the CODE keyboard the right keyboard for coding? - 2013-08-30 Solaar - Managing Logitech Unifying Receiver peripherals - 2013-08-29 LUGM meeting - 17.08.2013 - Mugg & Bean, Bagatelle - 2013-08-13 Adjust timezone of an AVM Fritz!Box 7390 - 2013-07-30 Speaker at developer conferences and user group meetings - 2013-07-24 Speaker at the German Visual FoxPro Developer Conference 2006 - 2013-07-24 Speaker at the German Visual FoxPro Developer Conference 2005 - 2013-07-24 Speaker at the German Visual FoxPro Developer Conference 2004 - 2013-07-24 Speaker at the German Visual FoxPro Developer Conference 2003 - 2013-07-24 Speaker at the German Visual FoxPro Developer Conference 2002 - 2013-07-24 Connecting Linux to WatchGuard Firebox SSL (OpenVPN client) - 2013-07-23 Feedback on meeting of the MSCC - 20.07.2013 - 2013-07-23 PHP Web Services by Lorna Jane Mitchell - Nice try - 2013-07-16 These aren't the droids you're looking for - 2013-07-08 Quoted on MVA Voices - 2013-07-04 Mauritius Software Craftsmanship Community (aka User Group) - 2013-07-01 New Pluralsight app for Android will bring variable speed - 2013-06-18 Feedback on meeting of the Linux User Group of Mauritius - 2013-06-17 FoxTales: Behind the Scenes at Fox Software by Kerry Nietz - 2013-06-10 The Niantic Project: Ingress by Felicia Hajra-Lee - 2013-06-05 Test your internet connection - Emtel Mobile Internet - 2013-05-31 Test your internet connection - Emtel Fixed Broadband - 2013-05-30 Communities - The importance of exchange and discussion - 2013-05-29 Pantel Penta WS802C (aka The white Orange tablet) - 2013-05-22 VMware Kernel Module Updater hangs on Ubuntu 13.04 - 2013-05-21 Programming Windows 8 Apps with HTML, CSS, and JavaScript - All you need in one title - 2013-05-20 Learning from jQuery - Solid fundament for experienced jQuery developers - 2013-05-20 Essentials for software developer or any computer job - 2013-05-13 Upgrade to Xubuntu 13.04 - Raring Ringtail - 2013-05-09 Side-load and read your mobi ebooks on Kindle for iOS - 2013-05-01 Side-load and read your ebooks on Kindle for Android - 2013-04-25 Playing Ingress by Niantic Labs in Mauritius - 2013-04-21 Attending the Mauritian Bloggers Meeting - 2013-04-21 The Alignment: Ingress by Thomas Greanias - 2013-04-06 Visualise your visitors with Google Maps Guestmap - 2013-04-04 Waiting for OUYA - 2013-04-03 Comment on 'Competent Mauritius' - 2013-04-02 Celebrating Earth Hour 2013 - 2013-03-23 Flexible Work style, meet your new Office - 2013-03-14 More content for MCSDs: Web Applications and Windows Store Apps using HTML5 - 2013-02-18 Torrential rain in Mauritius - some parts of the island - 2013-02-13 Driving Business Forward - A new approach to social media in Mauritius - 2013-01-28 Geocaching - Casela Bird Park (GC1BPF1) - 2013-01-26 Passed: Exam 70-480: Programming in HTML5 with JavaScript and CSS3 - 2012-12-18 Geocaching - World wide treasure hunt - 2012-12-07 Managing Joomla via Android - 2012-11-28 Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5 - 2012-11-16 Custom page sizes in paging dropdown in Telerik RadGrid - 2012-11-08 Taking web sites offline for demonstration - 2012-10-29 Taking web sites offline for demonstration on Galaxy Tablet - 2012-10-29 Taking web sites offline for demonstration on iPad - 2012-10-29 VMware Player 5.0 or VMware Workstation 9.0 after upgrade to Ubuntu 12.10 - 2012-10-23 Finally, upgrade from Nokia X3 to Samsung Galaxy S III - 2012-10-13 Software development stack 2012 - 2012-08-13 Synergy - easy share of keyboard and mouse between multiple computers - 2012-07-23 And again... VMware Player 4.0.4 on Ubuntu 12.04 (Precise Pangolin) - 2012-06-15 Update kernel patch for VMware Player 4.0.3 - 2012-05-04 Small hiccup with VMware Player after upgrading to Ubuntu 12.04 - 2012-04-30 New Year's resolution 2012 - 2012-01-03 Microsoft Virtual Academy - 2011-10-13 Some mail details about Orange Mauritius - 2011-02-11 Using Lightbox with _Screen - 2010-10-31 Using 3G/UMTS in Mauritius - 2010-10-20 Yes, I did it - Skydiving in Mauritius - 2010-10-16 Future of Active FoxPro Pages - secured - 2010-09-29 Using ext4 in VMware machine - 2010-07-29 Welcome - A new star is born! - 2010-05-27 Troubleshooting VMware on Ubuntu - 2010-04-16 Revamped Google Webmaster Tools - 2010-04-15 Community Day at SOS Kinderdorf Bambous - 2010-04-09 Install Adobe AIR on Ubuntu/Linux - 2010-03-19 AddThis and Google Analytics : Asynchronous Tracking Code Integration - 2010-03-03 Modified Ultimate Paypal Donations Module - 2010-02-17 Finally tweeting - 2010-02-08 Modified HP Router - 2010-01-30 Nichts ist unmöglich... - 2010-01-24 Spass mit der MessageBox() - 2010-01-24 Modified Mod LCA - 2010-01-23 World Map of Visitors - 2009-12-20 Sunshine (Codename: Moonwalk) - 2009-12-14 HTML Plugin for Active FoxPro Pages - 2009-12-14 COM Proxy for .NET - 2009-12-14 Presentations - 2009-12-14 Curriculum Vitae - 2009-12-14 Plans for the end of the year... - 2008-11-28 Family affairs - 2008-10-08 Welcome - A new star is born! \uD83D\uDE09 - 2008-09-20 InfoTech 2008 - 2008-08-08 Working with 'Parameter objects' instead of parameters - 2008-06-04 Running VMware Server on Linux (version 1.0.6 on xubuntu) - 2008-06-03 Running VMware Player on Linux (xubuntu Hardy Heron) - 2008-05-28 Some more articles migrated - 2008-04-24 Re-Blogged - 2008-03-19 Private projects added (at least started...) - 2008-03-19 Another MVP in Mauritius - 2008-03-14 Plans for Mauritius Software Developer User Group (MSDUG) - 2008-03-04 Emoticons re-activated - 2008-02-25 Upgrade to Windows Server Update Services 3.0 SP1 - 2008-02-25 Transfer of articles re-loaded - 2008-02-24 Transfer of articles from old blog database - 2008-02-18 Kung Shee Fat Choy - 2008-02-08 Loss of power - even w/o cyclone - 2008-02-03 Cyclone Warning Class 3 in Mauritius - 2008-01-31 Transfer of articles from Foxite community - 2008-01-18 Switch to English language - 2008-01-17 Es lebt wieder... - 2008-01-17 VFP Umgang mit Terminal Services - 2007-03-18 My First Cyclone - 2007-02-23 Jubel, Trubel, Heiterkeit - 2007-02-22 Super Hero or Super Villain? - 2007-01-31 Am Chiemsee im Winter - 2007-01-26 Irgendwas habe ich vergessen... ? - 2007-01-25 Microsoft Empower für ISVs - 2006-11-20 Tjoa... - 2006-11-19 Elfen Lied - 2006-11-18 Bug 'Resolved' - 2006-11-18 PowerShell 1.0 - 2006-11-16 13. Entwicklerkonferenz Visual FoxPro - Tag 2 - 2006-11-10 13. Entwicklerkonferenz Visual FoxPro - Tag 1 - 2006-11-09 The day before... - 2006-11-08 ATE auf der LOVE Roadshow - 2006-11-03 Decorator im praktischen Einsatz - 2006-11-02 Anmerkungen zum Decorator Design Pattern - 2006-11-01 All Saints' Day - oder auch nicht... - 2006-11-01 Flash-Präsentation zu VFP SP2 CTP - 2006-10-31 Semitransparente 'Forms' erstellen - 2006-10-29 Projekt - Sunshine - 2006-10-28 Devcon - Vorbereitung - 2006-10-27 Ego-Search und die Treffer - 2006-10-21 Family Website aktualisiert - 2006-10-21 Special Edition Using Visual FoxPro 6 - 2006-10-14 CTP für Sedna und Visual FoxPro 9.0 SP2 - 2006-10-14 RuntimeInstaller for Visual FoxPro 9.0 SP2 CTP - 2006-10-14 Session-Notes für VFP-Konferenz - 2006-10-12 ATE für Roadshow Office 2007, Windows Vista... - 2006-10-09 VFP Stammtisch Speyer nimmt an teil - 2006-10-01 Visual FoxPro Evangelist Program gestartet - 2006-09-28 Aufruf zur Unterstützung - Call for action - 2006-09-19 HTML-Tag nach VFP adaptiert - 2006-09-19 Kaum lobt man die Deutsche Bahn - 2006-09-16 Meine tropischen Inseln - 2006-09-14 Ländliches Brandenburg - 2006-09-13 Devcon Prag 2006 - Day 2 - 2006-09-12 Visual FoxPro Konferenz Frankfurt 2006 - Geplante Sessions - 2006-09-12 Devcon Prag 2006 - Day 1 - 2006-09-11 Die Bahn ist pünktlich... - 2006-09-09 Ein Königreich für eine Übernachtung - 2006-09-08 Darf's ein wenig Kultur sein? - 2006-09-07 Wieder einmal auf Achse - 2006-09-06 Eine Privatbrauerei mal anders - 2006-09-03 Arbeiten mit dem Visual Studio 2005 Team System - 2006-09-02 Zeitmanagement mit Microsoft Office Outlook - 2006-09-02 Mission from God - Community GetTogether - 2006-09-02 Vorbereitungen für Devcon Prag vollzogen - 2006-09-01 250 Artikel in gut 2 Jahren? - 2006-08-25 Kommentar zu 'Parameterobjekte' - 2006-08-25 SAP.Functions und weitere Experimente - 2006-08-24 Die Checkbox und ihr Enter/Return-Verhalten - 2006-08-17 VFP für Webausgabe von Daten - 2006-08-17 Experiences on Windows Live Writer... - 2006-08-14 Viel Wasser den Rhein runtergelaufen... - 2006-06-01 Schlaflos in ... - 2006-05-05 PoSh - Windows PowerShell - 2006-04-30 Effiziente Anwendungsentwicklung in VFP - 2006-04-29 ASP.NET und Visual FoxPro - 2006-04-29 Zwei unterschiedliche Datenbanken in ADO.NET verbinden - 2006-04-28 \"IIS 7 - Ein Web-Server der 'Next Generation'?\" - 2006-04-27 Windows Forms in VFP - 2006-04-26 New InfoPath template for blogging - 2006-04-22 InfoPath als Frontend für Blogs - 2006-04-22 VFPConversion - VFP and/or .NET? - 2006-04-22 Regular Expressions in VFP - 2006-04-22 Html Plugin 0.7.16 - 2006-04-21 April, April - Nachlese zum VFP Stammtisch - 2006-04-19 Dynamic-Link Libraries in VFP verwenden - 2006-04-19 .NET per COM nutzen - 2006-04-19 Internetcaches - unglaublich! - 2006-04-18 Was gibt's Neues von... Mike Oldfield? - 2006-04-17 Konspiratives Community-Treffen? - 2006-04-13 FCKeditor in Active FoxPro Pages verwenden - 2006-04-11 PathInfo Plugin für AFP - 2006-04-11 PATH_INFO oder wozu gibt es eigentlich RFCs? - 2006-04-10 AfpWiki Core Engine available - 2006-04-06 Design Pattern: Strategy - 2006-04-04 Design Pattern: Singleton - 2006-04-04 Polyphasenschlaf - Woche 1 - 2006-04-03 Experimente mit BlueTooth - 2006-04-03 Polyphasenschlaf - Tag 6 - 2006-04-02 Polyphasenschlaf - Tag 5 - 2006-04-01 Polyphasenschlaf - Tag 4 - 2006-03-31 Polyphasenschlaf - Tag 3 - 2006-03-30 Polyphasenschlaf - Tag 2 1/2 - 2006-03-29 Polyphasenschlaf - Tag 1 - 2006-03-28 Zuviel Zeit... oder? - 2006-03-28 Magazine von Level Extreme frei verfügbar - 2006-03-28 Polyphasenschlaf - Tag 0 - 2006-03-27 Bauen wir uns einen Webserver - 2006-03-26 Produktreife nähert sich... - 2006-03-25 FoxPro als embedded DB in .NET - 2006-03-23 VFPConversion.de - 2006-03-20 Objektorientierung für Anfänger... - 2006-03-20 Every rose has its thorn - 2006-03-11 Community, Community, Community! - 2006-03-11 Die CeBIT ruft... - 2006-03-10 VFPConversion.de - Clash of the Titans - 2006-02-22 Kommune 33 - 2006-02-10 Keynote ahead - 2006-02-08 5:45 Uhr und der Wecker klingelt - 2006-02-08 Neue Optik für den Hintergrund - 2006-02-01 Ein Tag hat 24 Stunden - 2006-01-28 On a mission from god - 2006-01-27 AJaX - der schnelle Küchenreiniger für die Website - 2006-01-26 New face, new service - 2006-01-25 Vista für Developer - 2006-01-22 Listed at FoxBlogs (TagCloud) - 2005-12-29 Long time ago... - 2005-12-28 Besinnliche Weihnachtszeit? - 2005-12-21 Fuchs NICHT vom Aussterben bedroht - 2005-12-17 Ausbildung: Fachinformatiker Anwendungsentwicklung - 2005-12-16 Freizeit? - 2005-12-15 Design Pattern: Decorator - 2005-12-03 Sie sind da... - 2005-11-25 dFPUG bietet Blogs für Mitglieder - 2005-11-23 Nice Smeagol... - 2005-11-23 Ja, ist denn schon Weihnachten? - 2005-11-18 Es geschehen noch Zeichen und Wunder! - 2005-11-17 Die Jungs bei Google haben Spass... - 2005-11-17 Es muss nicht immer FoxPro sein... - 2005-11-17 VFP Stammtisch Speyer - 16.11.2005 - 2005-11-14 Coverage der 12. VFP Entwicklerkonferenz - 2005-11-14 On the rail again... - 2005-11-13 God gave Rock 'n Roll to us. - 2005-10-26 Lily of the Valley - 2005-10-17 Kühe auf Nachtwanderung - 2005-10-15 AfpWiki wächst weiter... - 2005-10-13 Intranet Explorer - 2005-10-12 MSDN-Webcast-Serie zu Visual FoxPro 9.0 - 2005-10-10 Firefox 1.5 Beta - 2005-10-10 Versionskontrolle mit Subversion - 2005-10-10 Schreiben an die GEZ - 2005-10-02 Screenauflösung 5184x1200? - 2005-09-22 Browser-Upgrades - 2005-09-21 LINQ - auch mit VFP kein Problem: VOSQL - 2005-09-21 Stammtisch-Seite aktualisiert - 2005-09-20 VFP Stammtisch 21.09.2005 - Nachlese - 2005-09-20 Beliebige Zeichen in Dateipfaden - oder besser nicht... - 2005-09-20 Michael Moore bzw. die Qual der Wahl - 2005-09-19 Vorbereitungen für Stammtisch und Devcon - 2005-09-14 Kommentare zum Eisbär - 2005-09-13 SAP Arena! - 2005-09-07 Dumm gebabbelt iss glei! - 2005-09-07 VFP, SQL und .NET Konferenz der dFPUG 2005 - 2005-09-06 Vacation... - 2005-09-05 Büchermarathon - 2005-09-03 SAP Arena? - 2005-08-28 VFP Devcon 2005 - 2005-08-27 Rock im Brauhof 2005 - 2005-08-27 Die Happy & Culcha Candela - 2005-08-27 URLAUB! - 2005-08-24 Golo, der Gartenzwerg? - 2005-08-22 Normaler Tagesablauf? - 2005-08-22 VFP Stammtisch - 17.08.2005 - 2005-08-16 Finally... AFP FAQ wieder online - 2005-08-13 O'Reilly veröffentlicht Rezension - 2005-08-12 Using Microsoft InfoPath as blog frontend - 2005-08-02 International blogging - 2005-08-01 Pragmatic Version Control Using Subversion - 2005-07-29 VFP Stammtisch - Stuttgart/Herrenberg - 2005-07-15 Introducing Microsoft SQL Server 2005 For Developers - 2005-07-14 Browserwechsel angesagt - 2005-07-14 EULA vor Kauf lesen - 2005-07-13 Lost in the Twilight Zone... - 2005-07-12 MSDN zu VFP OLE DB Provider - 2005-07-12 Neues Release #585 - 2005-07-11 RSS Validation - 2005-07-10 Weitere Buchtitel zu VFP 9.0 - 2005-07-10 .NET Usertreffen Kaiserslautern - 2005-07-05 VFP Devcon Prag 2005 - Day 3 - 2005-06-22 Die Zukunft von VFP - 2005-06-13 Fäkalien-Alarm - Funstuff - 2005-06-13 Funstuff mit Pferden - 2005-06-12 Genius is indeed a heavy burden - 2005-06-12 C# Essentials, 2nd Edition - 2005-05-16 Practical PostgreSQL - 2005-05-10 Marathon, die Vierte? - 2005-05-08 Marathon, die Dritte - 2005-05-08 Marathon, die Zweite - 2005-05-06 Jeder sollte ein Hamsterfell besitzen - 2005-05-04 Marathon, die Erste - 2005-05-04 .NET Usertreffen vom Montag - 2005-05-03 Aktuelles Umfrageergebnis und weiterer Ansatz - 2005-05-02 Umfragen und die Ergebnisse... - 2005-05-02 Schlafes Bruder - 2005-04-29 In 80 Zeilen um die VFP-Tabelle - 2005-04-29 101 things you can do... - 2005-04-26 Randale? - Ein normaler Samstag - 2005-04-23 Ab in den Süden... - 2005-04-22 Zitate... - 2005-04-20 Nachlese: VFP Stammtisch Speyer im April - 2005-04-20 MSDN: Visual FoxPro mit Visual Studio 2005 - 2005-04-20 VFP Stammtisch - April 2005 - 2005-04-19 Pictures? Wanna pictures? - 2005-04-18 Unexpected error 0x8ffe2740 occurred - 2005-04-15 Nachlese zur Community Devcon 2005 - 2005-04-13 HTTP 404 - 2005-04-13 Daily Upgrade - 2005-04-11 Vorbereitungen für die Devcon laufen an... - 2005-04-11 Found by Google - 2005-04-10 Blog - Reloaded Again - 2005-04-08 Werbung verblödet... - 2005-04-08 New MSDN Forums - 2005-04-08 Ich will Feuerwehrmann werden - 2005-04-08 Frankfurter Buchmesse - 2005-04-07 VFP Umfrage von Microsoft - 2005-04-07 Expression evaluator failed (Error 67) - 2005-04-07 angryCoder - 2005-04-06 Schreiben Sie eine Online-Rezension - 2005-04-06 .NET for Visual FoxPro Developers - 2005-04-06 Blog - Reloaded - 2005-04-05 Es wird konkreter... - 2005-04-04 VFP Datenbank in .NET - 2005-04-04 VFP Roadshow Stuttgart - 3. Halbzeit - 2005-04-03 Surfin' HotSpots - 2005-04-02 Devcon Prag 2005 - Termin steht - 2005-03-29 Heute ist Ostern und Weihnachten gleichzeitig... - 2005-03-24 Clash of the browsers - 2005-03-13 MultiSelect-Komponente - 2005-03-05 SQL Server Workgroup Edition - 2005-02-27 Game another day - 2005-02-26 Nix Neues... - 2005-02-26 AfpWiki released - 2005-02-24 Noch ein Wiki - 2005-02-23 Überall Baustellen? - 2005-02-22 Fox Is Anywhere! - 2005-02-20 Community DevCon 2005 - 2005-02-19 VFP 9.0 Upgrade - 2005-02-19 Gegen die Kälte hilft nur ein heißes Bad - 2005-02-18 The future is now! - 2005-02-17 So kann ich net arbeiten! - 2005-02-15 Vorbereitungen für den Stammtisch - 2005-02-15 Wieviel NERD steckt in dir? - 2005-02-14 Viel Wasser läuft den Rhein runter... - 2005-01-15 C# unter Linux - 2004-12-01 Microsoft Communities - Ganz. Schön. Schlau. - 2004-12-01 Finally - 2004-11-14 Der Marathon beginnt... - 2004-11-10 Devcon: Ein erster Eindruck - 2004-11-09 Koffer packen und los geht's... - 2004-11-08 Quattro stagioni (CLIP Day Two) - 2004-10-21 Trio infernale (CLIP Day One) - 2004-10-20 Usertreffen... und kein Ende in Sicht - 2004-10-17 Die Rückkehr des Königs - 2004-10-17 Gastbesuch bei VFP Stammtisch Stuttgart - 2004-10-09 VFP/SQL/.NET Devcon 2004 in Frankfurt - 2004-10-09 FoxPro - das Urzeitmonster aus der Tiefe... - 2004-07-16 JavaScript macht Spass - 2004-07-13 Request timed out - 2004-07-13 Verspäteter Einstieg in die Welt der Blogs... - 2004-07-12"
  },
  "automate-nuget-package-creation-google-build.html": {
    "href": "automate-nuget-package-creation-google-build.html",
    "title": "Automate NuGet package creation using Google Build - Get Blogged by JoKi",
    "summary": "Automate NuGet package creation using Google Build Copy Markdown View Markdown GitHub Markdown Source Automate NuGet package creation using Google Build Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'automate-nuget-package-creation-google-build'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'automate-nuget-package-creation-google-build'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Automate NuGet package creation using Google Build') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-03-04 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); In a previous article on Create NuGet packages with Visual Studio / Visual Studio Code the software development lifecycle (SDLC) from build, pack, and deployment is done manually. Which can be quite time-consuming, tedious and tends to be error-prone given human interaction necessary. How about automating that process completely using Google Build? What's Google Build? Shortly said, Cloud Build is a service that executes your builds on Google Cloud. Or perhaps more precisely. Cloud Build, Google Cloud’s continuous integration (CI) and continuous delivery (CD) platform, lets you build software quickly across all languages. There are various machine types available and you can create a private pool for specific machine configurations or requirements. The interesting part for single developers and probably small(er) teams is that Google Build offers a free tier which includes 2,500 build-minutes (at the time of writing) which can be used to automate quite some tasks. Learn more about Google Cloud Build Pricing. How to get started? Navigate to Google Cloud Console, select your project and then enable the Google Build API for that project (and associated billing account). Or if you prefer to use the gcloud CLI tool run the following command. gcloud services enable cloudbuild.googleapis.com If you are prompted to authorize the gcloud CLI, enter your credentials. Once the API is enabled, you can use the gcloud CLI to manage your builds. For example, to create a build, run the following command: gcloud builds submit --config cloudbuild.yaml Which gives us the next glue. We need a build configuration file that instructs Google Build what to do. A basic build configuration file is described in the documentation: Create a build configuration file. Use build configuration file Writing such a build configuration file from scratch manually would be quite time consuming and probably a journey of trials and errors. Let Duet AI do the boring parts for us. How to use Google Build to automate the build process? Build, pack and push steps: - name: 'gcr.io/cloud-builders/dotnet' args: ['build', '-c', 'Release', 'src/Mscc.GenerativeAI.csproj'] - name: 'gcr.io/cloud-builders/dotnet' args: ['pack', '-c', 'Release', 'src/Mscc.GenerativeAI.csproj', '-o', 'output/'] - name: 'gcr.io/cloud-builders/dotnet' args: ['nuget', 'push', 'output/*.nupkg', '-k', '${_NUGET_API_KEY}', '-s', 'https://api.nuget.org/v3/index.json'] options: substitutions: _NUGET_API_KEY: YOUR_NUGET_API_KEY The generated cloudbuild.yaml file located in the project's root folder Those are exactly the steps we did manually previously. Build the .NET solution in Release configuration Pack and put the assembly into the output folder Push the generated package to NuGet using an API key That's a solid fundament and I didn't have to look up the name of the cloud builder. Cloud builders are container images with common languages and tools installed in them. Meaning, we are effectively pulling a Docker image by its container name from Google's Container Registry and use it to execute the arguments specified. The source code for these builders is available in the cloud builders GitHub repository. The Cloud Build developer community provides additional open-source builders that you can use to execute your tasks. And lastly, you can create your own custom builder for use in your builds. A few options As part of the options section you could also specify the machineType used to run those steps. The default machine type is e2-standard-2 with 2 CPUs. Which should be sufficient for the majority of build tasks and is also covered by the free tier of Google Build mentioned at the beginning. Now, the final part of the build config file is the value of the NuGet API key, and how it can be injected into the build process. Substituting variable values can be achieved in different ways. There is a list of default substitutions, the possibility to work with user-defined substitutions, and even dynamic substitutions which are evaluated during the building process. Submit to Google Build Again, asking Duet AI. How do I submit this configuration to Google Build with substitutions? To submit your build configuration file to Google Build, you can use the gcloud builds submit command. The following command will submit the build configuration file that you have provided to Google Build: gcloud builds submit --config cloudbuild.yaml --substitutions _NUGET_API_KEY=YOUR_NUGET_API_KEY This command will start a build using the configuration that you have provided, with the substitution _NUGET_API_KEY set to the value YOUR_NUGET_API_KEY. You can track the progress of your build by using the gcloud builds list command. Here's an interesting part of the build process. Starting Step #1 Step #1: Already have image (with digest): gcr.io/cloud-builders/dotnet Step #1: Microsoft (R) Build Engine version 16.2.37902+b5aaefc9f for .NET Core Step #1: Copyright (C) Microsoft Corporation. All rights reserved. Step #1: Step #1: Restore completed in 322.89 ms for /workspace/src/Mscc.GenerativeAI.csproj. Step #1: /usr/share/dotnet/sdk/2.1.818/Microsoft.Common.CurrentVersion.targets(1175,5): error MSB3644: The reference assemblies for .NETFramework,Version=v4.7.2 were not found. To resolve this, install the Developer Pack (SDK/Targeting Pack) for this framework version or retarget your application. You can download .NET Framework Developer Packs at https://aka.ms/msbuild/developerpacks [/workspace/src/Mscc.GenerativeAI.csproj] Finished Step #1 ERROR ERROR: build step 1 \"gcr.io/cloud-builders/dotnet\" failed: step exited with non-zero status: 1 Given the .NET versions used the default container image is going to fail. Why? Because it is based on the SDK for .NET Core 2.1! See GitHub The project used targets newer .NET versions, and so we need a newer Docker image that we can retrieve from the Microsoft Container Registry (MCR). So, to migrate to the official dotnet image, make the following changes to your cloudbuild.yaml: - name: 'gcr.io/cloud-builders/dotnet' + name: 'mcr.microsoft.com/dotnet/sdk:8.0' + entrypoint: 'dotnet' Use the official .NET 8.0 SDK Docker image Remove the original cloud builder provided by Google Container Registry (GCR) and add the most recent Docker image with .NET 8.0 SDK from the MCR. The updated build config file looks like so. steps: # Build the project - name: 'mcr.microsoft.com/dotnet/sdk:8.0' entrypoint: 'dotnet' args: ['build', '-c', 'Release', 'src/Mscc.GenerativeAI.csproj'] # Pack the NuGet package - name: 'mcr.microsoft.com/dotnet/sdk:8.0' entrypoint: 'dotnet' ... skipped for brevity Updated choice of Docker container from the MCR Which returns the expected outcome. ... skipped for brevity Step #0: Mscc.GenerativeAI -> /workspace/src/bin/Release/net8.0/Mscc.GenerativeAI.dll Step #0: Successfully created package '/workspace/src/bin/Release/Mscc.GenerativeAI.0.4.4.nupkg'. Step #0: Step #0: Build succeeded. Step #0: ... skipped for brevity Step #0: 142 Warning(s) Step #0: 0 Error(s) Step #0: Step #0: Time Elapsed 00:00:20.87 Finished Step #0 Starting Step #1 Step #1: Already have image (with digest): mcr.microsoft.com/dotnet/sdk:8.0 Step #1: MSBuild version 17.9.4+90725d08d for .NET Step #1: Determining projects to restore... Step #1: All projects are up-to-date for restore. Step #1: Successfully created package '/workspace/output/Mscc.GenerativeAI.0.4.4.nupkg'. Finished Step #1 Starting Step #2 Step #2: Already have image (with digest): mcr.microsoft.com/dotnet/sdk:8.0 Step #2: Pushing Mscc.GenerativeAI.0.4.4.nupkg to 'https://www.nuget.org/api/v2/package'... Step #2: PUT https://www.nuget.org/api/v2/package/ Step #2: Created https://www.nuget.org/api/v2/package/ 742ms Step #2: Your package was pushed. Finished Step #2 PUSH DONE A successfully completed build, pack and push of the NuGet package. Tune the build config file While using the dotnet Docker image to build, pack and push the NuGet package we might want to reduce the generated output a little bit. In particular regarding the first time experience and telemetry opt-out. Therefore, add the following env block into the options. options: env: - 'DOTNET_CLI_TELEMETRY_OPTOUT=true' - 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true' The resulting build configuration file should look similar to this. # outline the steps to build a .NET project, pack it as Nuget package and push it to Nuget source feed steps: # Build the project - name: 'mcr.microsoft.com/dotnet/sdk:8.0' entrypoint: 'dotnet' args: ['build', '-c', 'Release', 'src/Mscc.GenerativeAI.csproj'] # Pack the NuGet package - name: 'mcr.microsoft.com/dotnet/sdk:8.0' entrypoint: 'dotnet' args: ['pack', '-c', 'Release', 'src/Mscc.GenerativeAI.csproj', '-o', 'output/'] # Push the package to NuGet source feed - name: 'mcr.microsoft.com/dotnet/sdk:8.0' entrypoint: 'dotnet' args: ['nuget', 'push', 'output/*.nupkg', '-k', '${_NUGET_API_KEY}', '-s', 'https://api.nuget.org/v3/index.json'] options: substitutionOption: 'ALLOW_LOOSE' env: - 'DOTNET_CLI_TELEMETRY_OPTOUT=true' - 'DOTNET_SKIP_FIRST_TIME_EXPERIENCE=true' substitutions: _NUGET_API_KEY: YOUR_NUGET_API_KEY Eventually, it is considerable to use a separate API key for NuGet specifically used for the Google Build submission compared to the one used while doing it manually. I have several API keys in place. Where's the actual automation? Up till now, we have reduced manual labour by using a scripted approach. However, there is no automated process, at least not yet. To achieve an automated publication of a NuGet package we need a trigger. A trigger would initiate the Google Build submission based on a specific interaction, like say pushing a commit to the repository, merging a pull request, or perhaps run at a certain time every day. Cloud Build uses build triggers to enable CI/CD automation. You can configure triggers to listen for incoming events, such as when a new commit is pushed to a repository or when a pull request is initiated, and then automatically execute a build when new events come in. Create a Cloud Trigger to start an automated Cloud Build whenever there are changes to the source code. For that, we need to know where the code repository is located and establish a connection in Cloud Build. Your repositories in Cloud Source Repositories are connected to Cloud Build by default. If you are connecting an external repository, such as one hosted on GitHub or Bitbucket, you will need admin-level permissions on the repository to initially connect your repository to Cloud Build. Learn more about Create and manage build triggers. Connect a GitHub repository The sample source code is hosted on GitHub. This gives us two options, either we add a new remote to our local workspace and mirror the source code, or we create a trigger with a connection to the GitHub repository. Let's go for the connection. Open the Triggers section of Cloud Build in the Google Cloud Console. Click on Connect Repository in the top area and complete the connection. In case of first connection, GitHub asks for your consent to authorize Google Cloud Build to access your repositories. Eventually, the list of repositories on GitHub might be incomplete. Click on Edit repositories on GitHub to extend the Repository access. After adding the target repository, we can establish the connection to use in Cloud Build. Create a Cloud Trigger Last, we create a trigger using the connection. You can accept the suggested one at the end of the connection or create a new trigger. Click on Create sample trigger and you are done. Out of laziness I created the sample trigger but I'm going to edit it directly afterwards. Why? Because I don't like the suggested name, the suggested description, I don't want the trigger to monitor every branch but the main, and most importantly the substitution variables are missing. The following gcloud command creates the same trigger including the user-defined substitutions. gcloud builds triggers create github \\ --name=\"nuget-build-push\" \\ --description=\"Build, pack and push to NuGet on main branch\" \\ --region=\"us-central1\" \\ --repo-name=\"generative-ai\" \\ --repo-owner=\"mscraftsman\" \\ --branch-pattern=\"^main$\" \\ --build-config=\"cloudbuild.yaml\" \\ --substitutions=\"_NUGET_API_KEY=YOUR_NUGET_API_KEY\" The result should look like this. Created [https://cloudbuild.googleapis.com/v1/projects/...]. NAME CREATE_TIME STATUS nuget-build-push 2024-03-03T06:44:22+00:00 Learn more about Create and manage build triggers. Showtime! I made some changes to my source code and I'm about to push those commits. After click on Sync Changes, I open the History section of Cloud Build to check the status. You can directly observe the build steps and see the result of each in the details of a build. The substitution variables, here the NuGet API key, have been applied and the package has been pushed to nuget.org successfully. Now with a complete, working CI/CD pipeline using Google Cloud Build, I can concentrate on the source code and the implementation of new features. Gone are the boring and error-prone, manual steps to build, pack and push a new version of the NuGet package. What a breeze! But there is more... Avoid too many builds There are reasons you don't want to trigger a new build automatically, like i.e. fixing a typo, amending the documentation, or adding a new test, etc. Any kind of changes to the repository that would not justify / satisfy the deployment of a new version of a NuGet package. Triggers in Cloud Build provide at least two ways to reduce unnecessary builds and avoid wasting valuable build-minutes in your billing account. Use files filter In such case, you can either specify an Included files filter or an Ignored files filter using glob strings to define which files need to be affected or shall be ignored to trigger a build. The corresponding gcloud options are called --included-files and ignored-files to manage when a build is triggered or not. The structure of my public GitHub generative-ai used here in the article, has a folder called src that contains the source code of the actual NuGet package. With an included files filter of src/** I define that only changes affecting at least one included file will trigger a build. Note: ** is a recursive version of * which matches all files and directories in the selected directory and its subdirectories. Skipping a build trigger Additionally, there is the ability to skip a build trigger based on a single commit message. In such scenarios, you can include [skip ci] or [ci skip] in the commit message, and a build will not be invoked. If you want to run a build on that commit later, use the Run trigger button in the Triggers page. Learn more about Cloud Build triggers. Image credit: Gemini using prompt \"an image showing the creation of packages\" if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'automate-nuget-package-creation-google-build.html'; this.page.identifier = '9271ed1c97_idautomate-nuget-package-creation-google-build'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "azure-for-school.html": {
    "href": "azure-for-school.html",
    "title": "ICT skills at primary school - Get Blogged by JoKi",
    "summary": "ICT skills at primary school Copy Markdown View Markdown GitHub Markdown Source ICT skills at primary school Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'azure-for-school'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'azure-for-school'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'ICT skills at primary school') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-02-26 - Filed under LinuxPersonalDevelopment (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Our children have computer lectures at their primary school since this year. In general, it's a great idea that students are exposed to computer literacy at an early stage. But sometimes it comes with small hiccups. Like in our case... Curriculum, literature and exercise book Although our children have access to computers at home since a while already it is the curriculum of their primary school in regards to IT literacy that lead to this blog article. The title \"Let's Learn ICT Skills\" by the Mauritius Institute of Education (MIE) introduces Computer Fundamentals and Operations to young learners at primary school level. The textbook is divided into six units and covers first steps into the world of ICT. Starting with an orientation in Windows the title discusses the essential use of typical desktop applications to handle word processing, to introduce simple graphics and presentation skills, to cover basic functionality in spreadsheets and to venture into the unknown areas of the interweb. Each chapter has different learning objectives and introduces elementary skills in various applications. To keep matters easy the textbook is focused on Windows operating system and the Microsoft Office suite. Which is in general okay for the majority of primary school students. Well, most students... \uD83D\uDE09 Our situation to start with - Linux As a parent it is not easy to trust a full-fledged computer into the hands of your youngster(s) without fearing the whole system might be infested by viruses, malware and ransomware in shortest time. Especially given recent reports on various problems. Following my decision to provide our kids with family-friendly and security-enhanced tablets running on Amazon's Fire OS compared to regular Android, it was only right to provide them a similar experience on the desktop. At least in my point of view. Personally, it was important for me to have peace of mind knowing our children are using Linux based system. Don't get me wrong Microsoft has done a tremendous job to improve security over the last decade. It's just that I didn't want to purchase a new laptop for them and Linux runs just fine on older hardware. Instead of upgrading the available HP laptop from Windows Vista Business to latest Windows 10 I decided to install Xubuntu 17.04 originally. Some weeks back, I then upgraded their machine to Bionic Beaver (version 18.04) already, and they can \"beta-test\" the upcoming Ubuntu LTS version. After all, as more and more software is moved towards web applications it really doesn't matter anymore whether Firefox is run under Windows or Linux, does it? Additionally, they have access to LibreOffice, GIMP and other educational software packages like GCompris, and so forth. Well, the children's exercise book is explicitly covering Windows, some applications of the Microsoft Office suite as well as Paint.net - software that isn't available on Linux out of the box. Various approaches are possible Of course, there is no golden solution to this situation and multiple possibilities are given. All depending on circumstances, personal taste and eventual hardware constraints. Following, I would like to give you an overview of options - all of which I already used successfully in the past. Virtualisation This might come first in someone's mind and I have to agree with that. Installing a virtualisation software like Oracle VirtualBox, VMware Workstation or even qemu can be done easily and the the actual experience can be seamless. In our situation though is the existing hardware with a previous generation CPU and 2 GB RAM only the limiting factor to this approach. Using wine or CodeWeavers CrossOver Emulation software like wine or CodeWeavers CrossOver eliminate the necessity to install and run a complete virtualisation solution. The software provides an abstraction layer of native Windows API functionality and allows to install and run Windows software like the Microsoft Office suite among others directly on a Linux machine. Luckily, the hardware wouldn't be the limiting factor but I have to confess that it is my laziness to opt-in for this viable approach. Also, the first chapter in the kids' literature - Getting familiar with Windows - wouldn't be possible for them using this approach. Remote access Last but not least, providing remote access to an existing instance of a Windows system seems to be one of the easiest options. Here, the kids get to experience Windows directly and it doesn't need any resources on their Linux system. Using a software package like rdesktop or remmina enables a Linux user to connect to a Windows system via Remote Desktop Protocol (RDP). So far so good, but I'm not interested to provision a dedicated machine for this purpose at home. The system would be idle most of the time and consume a good chunk of electricity instead. As mentioned earlier I have used all those approaches successfully, and it is good fun to tinker around with them. But those are most likely options for an adult and not really suitable for a child attending primary school. A solution - Cloud-based virtual machine Taking the pro aspects of each of the approaches earlier I decided to provision a virtual machine running Windows 10 Professional in the cloud. Access to that machine is available using RDP and in regards to hardware constraints it requires an internet connection only. Actually, this suits me very well as it gives me more control at various levels: Local network: I can control at any time whether the kids' laptop gets access to our WiFi network or the internet based on simple authentication and routing configuration. Operating times: A virtual machine in Azure is fully controlled through the Azure portal. I can decide when the VM is running and when not. Hardware on demand: Provisioning hardware to the VM on Azure is just a few clicks and a reboot away. Data exchange: Synchronisation of files between the local Linux laptop and the Windows machine in Azure is based on cloud storage providers like OneDrive, Google Drive, Dropbox, etc. Meaning backup of files is integrated and additional devices like their tablets can be added easily. Later on, if the VM isn't needed anymore or in case the children totally messed it up I don't have to worry about anything. The VM gets decommissioned and can be provisioned again within minutes if needed. Azure configuration and fine-tuning To start with this educational system for my children I went into the Azure Portal and created a new virtual machine using the Windows 10 Pro image. To keep it nice and smooth I also created a designated resource group to isolate it from other business-related activities. Size of the VM I chose a (hopefully) decent hardware setup running the virtual machine on a Standard B4MS (4 cores with 16 GB RAM) tier. This should be sufficient enough for Microsoft Office 2016, Paint.net and Firefox. Auto-shutdown Also, I activated the auto-shutdown feature which restricts the use of the system until a specified time, and helps me to save a heap of money, too. The main purpose of that VM is to allow the children to follow the exercises and steps in their school book. At the given time the system simply shuts down, and it's dinner time in the off-line world. Starting the VM Now that we know how to stop the VM we should have a look about how to start it. There are multiple choices available. Most obvious you can launch the virtual machine via the Azure Portal itself. Nothing surprising here. Next, Microsoft offers the free Azure mobile app for Android and iOS to stay connected to your Azure resources. This is quite neat to manage, monitor and operate Azure on the go. And then there is azure-cli - the Command-line tools for Azure - which gives you the next generation multi-platform command line experience for Azure. $ az /\\ / \\ _____ _ _ ___ _ / /\\ \\ |_ / | | | \\'__/ _\\ / ____ \\ / /| |_| | | | __/ /_/ _\\/___|__,_|_| ___| Welcome to the cool new Azure CLI! Usually, I have Visual Studio Code open almost the whole day and starting the kids' virtual machine is done using the Azure CLI Tools extension. I'm currently using the following .azcli file to manage that VM: # Logging into Azure az login # Starting kids' VM on Azure az vm start -g Personal -n windows4kids # Stopping kids' VM az vm stop -g Personal -n windows4kids --no-wait The az login triggers the device login on Azure and after entering a generated code to authenticate your machine you get access to your resources on Azure, like this: Accessing the VM Windows machines on Azure are accessed via RDP and Linux has a variety of client applications for that protocol. In the portal you should assign a static domain name to your VM as the public IP address is most likely to change between daily uses. The portal allows you to download the Connect parameters as a .rdp file that you can open in any text editor on Linux. Using the details from the .rdp it is possible to set up a new connection in remmina for future use. I'm storing the password to keep it simple for the children to access their new Windows machine. Now, remmina is configured to start automatically after they logged into their account and the Windows VM on Azure is easy accessible via shortcut from the system tray area. Give it a try - Azure free credit Microsoft gives new sign-ups on Azure an initial credit that allows you to explore the various options and get yourself familiar with the available resources. Why don't you give it a try? if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'azure-for-school.html'; this.page.identifier = '9271ed1c97_idazure-for-school'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "azuredev-munich.html": {
    "href": "azuredev-munich.html",
    "title": "Azure DEV Meetup Munich - Get Blogged by JoKi",
    "summary": "Azure DEV Meetup Munich Copy Markdown View Markdown GitHub Markdown Source Azure DEV Meetup Munich Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'azuredev-munich'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'azuredev-munich'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Azure DEV Meetup Munich') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-04-24 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); While I was on a business trip to Germany back in February I thought that it might be a good idea to check out some of the local user groups. During the day I would be at the customer's site but I wanted to use the evening hours to discover new opportunities. Turned out that there are quite a number of active user groups in Munich. However their dates either didn't fit my schedule or the maximum limit of attendees had been reached already or I would have been too late to join the event because of distance and public transport. Luckily, the Azure DEV Meetup Munich user group (@AzureDevMuc) organised an evening event - Azure Cosmos DB: Yes, zu NoSQL mit der MongoDB-API - suitable for my working hours, and it turned out that the venue wasn't that far from the hotel either. It took me less than 30 minutes to get there. I had to ride two lines of the U-Bahn and take a short stroll by foot from the station. Actually, I arrived a few minutes after the official starting time, being slightly late. Shame on me? Well, luckily not really because the guest speaker for the night was still stuck in traffic. A few people were already there, enjoying fresh pizza and chilled drinks. Ranging from soft drinks and apple juice to various types of beer. After being welcomed by one of the hosts, Thomas Pentenrieder, I was asked to note down my name on the attendance sheet - on paper BTW -, and stick one of those fancy name tag badges you might know from the movies onto my chest. \"Hi, my name is 'JoKi'\"... Cool, I really like those stickers and I'm going to source them for our future user group meetings back in Mauritius. With the name being exposed on that sticker everyone felt at ease to initiate a conversation. It's definitely an icebreaker and helps to avoid those awkward questions about hearing a name properly or not. It quickly turned out that Thomas is a Microsoft Windows Insider MVP like myself and we talked a little bit about the latest builds and the new features they are offering. And of course, we were looking forward to meet soon at the Microsoft MVP Summit, mid of March in Seattle. Well, that has been scraped... While munching some slices of delicious pizza others got interested into my technical and professional background as well as how I ended up in Mauritius. Interestingly, some participants knew about Visual FoxPro but were surprised that it still in productive use. Oh yes, it is and it's paying the bills quite nicely too. Inspired by the Aqualung branded T-shirt I finally engaged in a conversation with the other co-host of the evening, Ralf Richter. Turns out that he goes regularly diving in the nearby lakes in the surroundings of Munich, and that he had been to quite a number of dive spots worldwide. IIRC, Mauritius might be still on his list of 'wanna-go' destinations. More people joined the meeting, and there had been tons of food and drinks made available. Roughly half an hour after our guest speaker, Gregor Biswanger (@BFreakout), arrived. Given the community nature of the evening there was enough time to give Gregor a quick break to refresh and to taste some of those delicious pizza. Funnily, it turned out that Gregor and I have several common friends. First and most remarkable, Markus and Marina. They witnessed our wedding while they were on their honeymoon in Mauritius. I know both since my early days of working with VFP. Actually, Markus and I worked on the same project(s) back then, we met regularly at the annual dFPUG Entwicklerkonferenz (German FoxPro User Group Developer Conference), and we both run a regional user group of the dFPUG. Turns out that Marina fixed up Gregor's computer at some stage and Markus got him into public speaking back then. Fascinating! Our conversation could have lasted for hours. However it was time to get started with the technical topic of the evening. Yes, zu NoSQL! The topic of the evening was about MongoDB and then Azure Cosmos DB. In particular about how to use the MongoDB-API of Azure Cosmos DB. Do you know where the name MongoDB actually originates from? (Answer at the end of the article) Gregor started with a journey of his own experience coming from relational database modelling, and how he started to embrace and love the advantages of NoSQL. With permission, he showed the entity relationship model (ERM) of a database used for the storage of recipes. In 'classic' manner all data had been normalised and relationships gave information how that data was bound together to specify a recipe. Using a decision matrix, he called it NoSQL Compass, Gregor went through the DB schema and how the data stored in its relations could be stored in NoSQL fashion. Well, after a few iterations we came to the conclusion that a recipe itself simply forms a document and therefore is a whole in itself. Absolutely no need to split it into whatever chunks. But you might now say 'how would you query the data for a specific piece of information?' Nothing to worry about, the query syntax for NoSQL provides the needed flexibility. However, in case of a recipe application you would most likely be interested in the whole recipe and less in parts of it. You'll find more details about that NoSQL Compass and how to use the decision matrix in the corresponding slide deck offered by Gregor (see below for links). Live stream and recording The technical part of the evening was live streamed and the recording is hosted on Youtube. Additionally, Gregor generously put his slide decks online and provided another conference recording of his talk on that topic. Here are all the links: Azure Dev Munich on Youtube Video: Azure Cosmos DB: Yes, zu NoSQL mit der MongoDB-API Slide deck: Fachmodell-First - Einstieg in das NoSQL-Schema-Design Slide deck: Yes zu NoSQL mit MongoDB - Für .NET-Entwickler Video: Yes zu NoSQL mit MongoDB für .NET-Entwickler! Most of the content is in German language. Perhaps you'll give it a try and then feel free to reach out to me for some translation or questions you might have on MongoDB or the MongoDB-API for Azure Cosmos DB. Electron.NET It shall be noted that Gregor is also a maintainer of Electron.NET - Build cross platform desktop apps with ASP.NET Core. I haven't tried that platform yet but made a note to myself to consider it for a future side project or viable assignment. The 'After-Party'... That's when the real fun starts. Usually after a hard day's work it's time to shake off the stress and tensions and move on to chill and relax. Similar happened after the meetup concluded. Most people gave their greetings and left to head back home. However given that I had nothing really to do and Gregor wasn't tired enough to hit the sack immediately, we picked up the conversation from earlier and talked quite a lot about communities - in Germany and in Mauritius. I told him about the development here on the island over the past few years, and he seemed also interested to come and speak at our Developers Conference some day. Of course, we also talked about the Microsoft MVP program and how it has changed over time. As we are both active MVPs we discussed on a few NDA related topics as well. Was really interesting to have this kind of offline exchange. And Gregor was a bit surprised that I had been awarded earlier than him. However he got more awards so far. \uD83D\uDE09 As the conversation went on we discovered more and more common friends and acquaintances, and it was really funny to explore those connections and how they are linked between each other. It was a great community event and my decision to attend exceeded all kind of expectations. Kudos to Ralf and Thomas who organised the evening and all the best to run the Azure DEV Meetup Munich. Thanks to Gregor for the entertaining talk and demos on MongoDB and how to use Azure Cosmos DB with the MongoDB-API. Answer: humongous - meaning: huge, enormous, extremely large (Merriam-Webster) if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'azuredev-munich.html'; this.page.identifier = '9271ed1c97_idazuredev-munich'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "bauen-wir-uns-einen-webserver.html": {
    "href": "bauen-wir-uns-einen-webserver.html",
    "title": "Bauen wir uns einen Webserver - Get Blogged by JoKi",
    "summary": "Bauen wir uns einen Webserver Copy Markdown View Markdown GitHub Markdown Source Bauen wir uns einen Webserver Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'bauen-wir-uns-einen-webserver'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'bauen-wir-uns-einen-webserver'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Bauen wir uns einen Webserver') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-26 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ausgehend von zwei konkreten Vorlagen (Buch mit Codesnipplet und Eeva) ergab sich direkt die Möglichkeit zur Erstellung eines lokalen Webservers. Dieser wird sozusagen \"afp-enabled\" gebaut. Dies ermöglicht es uns, dass wir autark ohne externen Webserver arbeiten können. Mögliche Anwendungsszenarien sind hierbei die Auslieferung auf CD und vorallem zum vereinfachten Entwickeln und Debuggen. Die aktuelle Implementierung basiert auf dem Microsoft Winsock2 ActiveX Control. Dieses hat jedoch nach MSKB und einigen Onlinebeiträgen kleinere Bugs und die Limitierung auf 5 concurrent Connections. Naja, nicht weiter brutal, da man jederzeit eine andere Socketimplementierung drunter setzen könnte. Die nächste Ausbaustufe wird die Implementierung des Webservers in .NET 1.1 und 2.0 sein. Hier liegt der rudimentäre Code in C# bereits vor. Der Ansatz basiert auf der Verwendung der System.Net.Sockets.TcpListener Klasse in Verbindung mit System.Threading - schliesslich soll der Server ja mehrere Clients zeitgleich bedienen können. Naja, eventuell gönne ich mir mal einen Blick in den QUellcode von Cassini bzw. XSP. Hier könnten sich potentiell noch Anregungen ergeben, wie man auch gleich ASP.NET integriert und per Factory Pattern sowie Plugins weitere Extension anbinden könnte... Naja, mal sehen, wie weit wir den Spass treiben wollen. Aktuell bereitet mir die Konfiguration und die sonstigen Information wie Mimetypes, Logging, Fehlercodes und anderes ein wenig Kopfzerbrechen. Aber auch hier werden sich sicherlich coole Möglichkeiten finden. Und wenn's \"nur\" der Zugriff über Ole DB auf VFP Tabellen ist. Hm, dürfte wahrscheinlich die einfachste Lösung darstellen. Dadurch können beide Server (VFP und .NET) hüstel alle drei Server, über die identische Konfiguration und sonstigem Beiwerk gefahren werden. Spart mir Nerven und Zeit. Potentiell kann man sogar partitionieren, so dass multiple Instanzen möglich sind. Evtl. sogar mit der Option, dass sich der Webserver seinen Port selbständig im unpriviligierten Bereich sucht und bindet. Hierbei würden wir dann lediglich den letzten aktiven Port pro Implementierung irgendwo ablegen. Dies verkürzt im Normalfall den Startprozess. [code comment=\"Portselektion beim Serverstart\"]Start der Anwendung Konfig einlesen (Port, etc) Port prüfen wenn bound, Schleife mit Randomport laufen lassen neuen Port sichern Ausführung[/code] Die zentrale Konfiguration könnte man entweder als DBF und/oder XML ablegen. Wahrscheinlich dürfte es am einfachsten für beide Systeme sein, wenn man eine XML-Datei eines DataSet / XmlAdapter verwendet. Nativer Zugriff auf beiden Seiten... mal testen. Weitere Features? Aktuell noch keinen Plan... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'bauen-wir-uns-einen-webserver.html'; this.page.identifier = '9271ed1c97_idbauen-wir-uns-einen-webserver'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "bchermarathon.html": {
    "href": "bchermarathon.html",
    "title": "Büchermarathon - Get Blogged by JoKi",
    "summary": "Büchermarathon Copy Markdown View Markdown GitHub Markdown Source Büchermarathon Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'bchermarathon'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'bchermarathon'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Büchermarathon') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-03 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Was macht man in seiner Urlaubszeit? - Richtig, Bücher lesen. Aber nicht ein, nicht zwei, sondern mal lockere 4 Romane en block. Irgendwie habe ich das mal wieder nach sehr sehr langer Zeit gebraucht. Herrje, wenn ich an meine Zeit in der Oberstufe zurückdenke, dann habe ich pro Woche 1.5 bis 2 Bücher in mich reingesaugt - lesen wäre zu gutmütig formuliert. Well, während des Studiums hatte sich diese Art der Freizeitgestaltung ein wenig verschoben. Anstelle von Belletristik war stupides Wälzen von Fachliteratur angesagt. Mann, was ein Scheißkonzept! Ehrlich gesagt gibt es bei weitem bessere und effizientere Möglichkeiten sich universitäres Wissen anzueignen als die Variante, die einem an der Hochschule untergejubelt wird. Nunja, zu dem Zeitpunkt ist es ja bereits zu spät... Gerade mit der begleitenden Beobachtung der schulischen Leistungen meiner Geschwister ergaben sich immer und immer wieder zweifelnde Momente! Wieso werden simple Sachverhalte auf so umständliche und ehrlich sogar noch verwirrende Weise an die Kids gebracht. Irgendwie meine ich die Ursache vllt zu erahnen: Es fehlt an Phantasie. Besonders Kids kann man wesentlich mehr Wissen durch Kontexte und Allegorien vermitteln, aber dieses Potential verpufft in den heutigen Klassensälen, oder? Aber... ich schweife mal wieder ab. Zurück zu meinem Belletristik-Exkurs. In den letzten Tagen - also während meines Urlaubs - habe ich mir die ersten vier Bände von Stephen Kings Epos 'Der Dunkle Turm' (Schwarz, drei, tot und Glas) reingezogen. Das sind geschätzte 2.200+ Seiten Material gewesen. Achja, und der fünfte Band Wolfsmond liegt startbereit und wird sich zu den anderen in den nächsten Stunden gesellen. Naja, ich hab' während meines Urlaubs nicht nur gelesen, aber es war sehr erfrischend und belebend - ich hab' sei langem nicht mehr so herzhaft beim Lesen gelacht wie bei diesen Titeln. Außerdem habe ich selbst wieder etwas gewonnen! Und... einen konkreten Vorsatz bis Ende 2006: Lesen der Top 50 der Buchtitel, die das ZDF unter seinen Zuschauern wählen liess. Ich freu' mich echt drauf. Nicht nur weil ich bereits gute 30% gelesen habe, sondern weil auch etliche Werke dabei sind, die eh seit einiger Zeit auf meiner ToRead-Liste standen und stehen. This is my call: Lest mehr Bücher - es kitzelt die Seele... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'bchermarathon.html'; this.page.identifier = '9271ed1c97_idbchermarathon'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "beliebige-zeichen-in-dateipfaden---oder-besser-nicht---.html": {
    "href": "beliebige-zeichen-in-dateipfaden---oder-besser-nicht---.html",
    "title": "Beliebige Zeichen in Dateipfaden - oder besser nicht... - Get Blogged by JoKi",
    "summary": "Beliebige Zeichen in Dateipfaden - oder besser nicht... Copy Markdown View Markdown GitHub Markdown Source Beliebige Zeichen in Dateipfaden - oder besser nicht... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'beliebige-zeichen-in-dateipfaden---oder-besser-nicht---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'beliebige-zeichen-in-dateipfaden---oder-besser-nicht---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Beliebige Zeichen in Dateipfaden - oder besser nicht...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-20 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Well, wie soll ich es anfangen... ? Starten wir bei einem netten Kommentar aus einem Forum, welches ich mir unregelmäßig zu Gemüte ziehe und versuche Hilfestellungen zu geben: *CHM-Hilfedatei läuft nicht vom Netz Es liegt in dem fall nicht an der tiefe/länge eines Verzeichnissnamens sondern ob bestimmte Zeichen im Verzeichnissname vorkommen! zB darf das \"#\" Zeichen nicht im Verzeichniss Namen enthalten sein sonst kommt der von dir beschriebene fehler! * Nun, wenn man sich die genutzte Technologie - komprimiertes HTML - betrachtet, dann ist die Ursache ganz einfach. Das Gatter-Symbol '#' wird in HTML zur Markierung von Sprungzielen innerhalb des Dokuments genutzt. Daher wird alles nach dem '#' als Sprungziel interpretiert, obwohl wir eigentlich noch gar nicht zu weit wären, sondern erst beim Pfad sind... Tjoa, wie kommt man auch auf die Idee dieses Sonderzeichen im Pfad zu verwenden - tsttss aber auch... Es ist zwar nicht ganz so brutal wie mit dem ungewünschten Leerzeichen, aber es zeigt doch wieder einmal, dass die werbeversprochene Freiheit in Pfaden und Dateinamen sehr oberflächlich und leicht brüchig ist. Schade eigentlich... Hier besteht noch einiges an Nachholbedarf! Evtl. mal heute Abend den Test unter Windows Vista realisieren und einen entsprechenden Bugreport verfassen. Ansonsten, bleibt gegenwärtig nur meine Empfehlung: Don't do this @ home, folks. Leerzeichen und jegliche Art von Sonderzeichen haben definitiv nix im Pfad oder Dateinamen zu suchen. Keep it simple, stupid! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'beliebige-zeichen-in-dateipfaden---oder-besser-nicht---.html'; this.page.identifier = '9271ed1c97_idbeliebige-zeichen-in-dateipfaden---oder-besser-nicht---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "berall-baustellen.html": {
    "href": "berall-baustellen.html",
    "title": "Überall Baustellen? - Get Blogged by JoKi",
    "summary": "Überall Baustellen? Copy Markdown View Markdown GitHub Markdown Source Überall Baustellen? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'berall-baustellen'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'berall-baustellen'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Überall Baustellen?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-22 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es ist irgendwie unglaublich, aber dennoch nicht von der Hand zu weisen - überall Baustellen. Mein Wohnzimmer sieht aus, als ob ein BobCat durchgejagt wäre. Kabel, Festplatte, ausgeweidete Computer - ein technisches Schlachtfeld. Bleibt nur die Frage, wer als Sieger hervorging. Ebenso sieht es derzeit softwareseitig aus... Da wären zuerst einmal die AFP FAQ, dann das HTML Plugin for Active FoxPro Pages wartet schon sehnsüchtig auf die Korrekturen und Erweiterungen - puuh, stellt sich nur die Frage, wo die letzten Änderungen rumfliegen. Im Repository ist derzeit 0.7.4; ich war bereits bei 0.7.6 - komisch, komisch \uD83D\uDE44 Dann wäre da noch das ein oder andere weitere Freizeitprojekt, aber was soll ich euch mit sowas langweilen? Was hätten wir denn noch so alles? Nun, zum Glück gibt's draußen derzeit keine Baustellen. Allein wegen des Wetters schon Glück gehabt. Wobei der Schnee eine willkommene Abwechselung darstellt. Sofern man nicht draußen rumgeistern muß. Aber interessanterweise konnte ich die ersten spanischen Postings zu Artikeln bzw. im FAQuarium feststellen. Wow, nun bin ich ein wenig in Zugzwang, aber auch für diese 'Baustelle' wird sich eine Lösung finden. Ich wünsch' euch noch eine erholsame Nacht. Image courtesy of Eric Wang if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'berall-baustellen.html'; this.page.identifier = '9271ed1c97_idberall-baustellen'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "besinnliche-weihnachtszeit.html": {
    "href": "besinnliche-weihnachtszeit.html",
    "title": "Besinnliche Weihnachtszeit? - Get Blogged by JoKi",
    "summary": "Besinnliche Weihnachtszeit? Copy Markdown View Markdown GitHub Markdown Source Besinnliche Weihnachtszeit? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'besinnliche-weihnachtszeit'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'besinnliche-weihnachtszeit'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Besinnliche Weihnachtszeit?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-12-21 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Aktuell könnte ich Bäume ausreissen und irgendwelche gewalttätigen Aktivitäten durchführen. Warum? Weil mich ab und zu meine Mitmenschen ein wenig abnerven. Insbesondere auf Grund irgendwelcher dümmlicher Aussagen... Okay, ich glaub' 'ne verschärfte Runde Joggen oder Sandsack-Kloppen passt auch zum Adrenalin abbauen. Herrje, manchmal stellt man sich doch echt die Frage, wurde diejenige oder derjenige als Kind mit dem Klammersack gepudert oder durch den Trog gezogen? grummel Hachja, das Blog als mentaler Stressabbau, tjoa, Leute, das müsst / dürft ihr nun aushalten. Schliesslich ist das MEIN MOND! Jaaa, genau... MEIN MOND! \uD83E\uDD2A Ach, druff gesch***, ich zieh' mir noch 2 Liter Tee in die Birne, werd' mir heute Abend potentiell Fight Club oder Die Passion Christi reinziehen und dann geht's mir wahrscheinlich wieder wesentlich besser. Nur meine aktuelle Stimmung ist mehr als GEREIZT. Nunja, vllt. auch einfach nur ein Kennzeichen, dass ich menschlich bin und entsprechend reagiere. Who knows... Ciao, ciao, folks. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'besinnliche-weihnachtszeit.html'; this.page.identifier = '9271ed1c97_idbesinnliche-weihnachtszeit'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "blog---reloaded.html": {
    "href": "blog---reloaded.html",
    "title": "Blog - Reloaded - Get Blogged by JoKi",
    "summary": "Blog - Reloaded Copy Markdown View Markdown GitHub Markdown Source Blog - Reloaded Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'blog---reloaded'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'blog---reloaded'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Blog - Reloaded') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-05 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich hoffe, dass die Optik gleich ein wenig ins Auge gesprungen ist... Was haltet ihr vom geänderten Aufbau des Blogs? Neben der Umstellung von 2 auf 3 Spalten sind noch ein paar Ergänzungen und Umverteilungen der einzelnen Parts links und rechts der Beiträge erfolgt. Außerdem habe ich dem Aspekt Community wesentlich mehr Aufmerksamkeit spendiert. Desweiteren gibt's ein paar Kontaktmöglichkeiten für euch... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'blog---reloaded.html'; this.page.identifier = '9271ed1c97_idblog---reloaded'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "blog-reloaded-again.html": {
    "href": "blog-reloaded-again.html",
    "title": "Blog - Reloaded Again - Get Blogged by JoKi",
    "summary": "Blog - Reloaded Again Copy Markdown View Markdown GitHub Markdown Source Blog - Reloaded Again Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'blog-reloaded-again'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'blog-reloaded-again'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Blog - Reloaded Again') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Tjoa, ab und zu sind doch noch ein paar Kniffe notwendig, damit's auch mit dem Nachbarn klappt... Nach einigem Feedback durch Golo konnte ich noch die ein oder andere Veränderung in den Styles realisieren und die Optik ein klein wenig verbessern. Apropos Optik - Danke Alex, für das polierte CLIP-Logo. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'blog-reloaded-again.html'; this.page.identifier = '9271ed1c97_idblog-reloaded-again'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "blog.html": {
    "href": "blog.html",
    "title": "Blogging about bits and bytes... - Get Blogged by JoKi",
    "summary": "Blogging about bits and bytes... Copy Markdown View Markdown GitHub Markdown Source Blogging about bits and bytes... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'blog'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'blog'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Blogging about bits and bytes...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); Welcome to my personal space On this site I'm writing about various topics in the area of information technology and community activities. From time to time there might be something exotic in-between. I started this blog back in 2004, and have written over 400 articles so far. Technical highlights Practice makes perfect and excellent craftsmanship is based on constant and evolving practice. Essentials for software developer or any computer job Connecting Linux to WatchGuard Firebox SSL (OpenVPN client) Series on IPv6 configuration on Linux Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5 Community involvements Apart from taking care of the largest IT community in Mauritius, the Mauritius Software Craftsmanship Community (MSCC) I'm a registered member of the Linux User Group Mauritius (LUGM), and I contribute to various online communities, too. Eventually, the following posts might be interesting to read. Communities - The importance of exchange and discussion Introduction to Mauritius Software Craftsmanship Community Ubuntu Jam at the University of Mauritius: Sharing information about the XFce4 desktop environment Launch Event: Windows 10 and Visual Studio 2015: Speaking about Visual Studio Code on Linux"
  },
  "browser-upgrades.html": {
    "href": "browser-upgrades.html",
    "title": "Browser-Upgrades - Get Blogged by JoKi",
    "summary": "Browser-Upgrades Copy Markdown View Markdown GitHub Markdown Source Browser-Upgrades Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'browser-upgrades'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'browser-upgrades'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Browser-Upgrades') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-21 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Heute ist Browser-Upgrade-Tag... Zumindest habe ich bereits 2 Browser auf meiner Kiste aktualisiert: Mozilla 1.7.12 Firefox 1.5 Beta 1 Schade, dass es den IE 7 noch nicht für Windows Server 2003 öffentlich gibt. Eigentlich könnte ich noch den inzwischen ohne-Werbebanner-kostenfreien Opera 8.x installieren, aber ich kann mich nicht dazu durchringen. Nunja, die Tage vllt. mal... wer weiß. Ansonsten, hab' ich einiges zu coden, also bis denne... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'browser-upgrades.html'; this.page.identifier = '9271ed1c97_idbrowser-upgrades'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "browserwechsel-angesagt.html": {
    "href": "browserwechsel-angesagt.html",
    "title": "Browserwechsel angesagt - Get Blogged by JoKi",
    "summary": "Browserwechsel angesagt Copy Markdown View Markdown GitHub Markdown Source Browserwechsel angesagt Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'browserwechsel-angesagt'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'browserwechsel-angesagt'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Browserwechsel angesagt') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-14 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Falls ihr mal keinen Bock habt am PC durchs Internet zu surfen, dann probiert's doch mal offline. Entweder selbst oder mit einem weiblichen Wesen aus dem näheren Umfeld dürfte bei diesem Accessoire ein Heidenspass aufkommen: https://www.getdigital.de/index/0xdb/lng/1 Warning: Vermeidet die Realität... Hm, ob's die Teile auch als Shorts gibt? Und wieso gibt's eigentlich keinen HTTP 500 - Internal Server Error? if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'browserwechsel-angesagt.html'; this.page.identifier = '9271ed1c97_idbrowserwechsel-angesagt'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "bug-resolved.html": {
    "href": "bug-resolved.html",
    "title": "Bug 'Resolved' - Get Blogged by JoKi",
    "summary": "Bug 'Resolved' Copy Markdown View Markdown GitHub Markdown Source Bug 'Resolved' Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'bug-resolved'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'bug-resolved'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Bug 'Resolved'') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-18 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Zwar habe ich die Mail von Microsoft schon einigen Tagen erhalten, aber erst heute wirklich die Zeit und Muße darüber zu schreiben. Ich hoffe, dass dies bei euch auf Verständnis stößt. Worum geht's eigentlich? Nun, da über das Service Pack 2 für Visual FoxPro 9.0 ja die wildesten Gerüchte unterwegs sind, konzentriere ich mich lieber auf die Möglichkeiten des Feedbacks an Microsoft zur Verbesserung des Produkts. Zu sämtlichen Microsoft Produkten kann man über das MSDN Product Feedback Center Anfragen, Wünsche und Fehlermeldungen online einstellen. Da wir im laufenden Projekt über eine Kuriosität mit dem Grid gestolpert sind, hatte ich einen entsprechenden Bugreport samt Democode eingestellt. Nun, die Tage kam die Mail, dass der Eintrag als 'Resolved' klassizifiert ist und dies bedeutet, dass es mit dem Service Pack 2 behoben sein wird. Mal sehen, ob ich es mit dem nächsten CTP bereits prüfen kann. Details dazu findet ihr im MSDN Product Feedback Center: Grid scrolling defect while using Columns with Visible = .F. Wie sieht es mit euren entdeckten Problemen in Visual FoxPro aus? Meldet diese an Microsoft und die Chancen steigen, dass ihr euch künftig nicht mehr darüber ärgern oder beschweren müsst. Also, ran den Code... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'bug-resolved.html'; this.page.identifier = '9271ed1c97_idbug-resolved'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "c-corner-conference-2017.html": {
    "href": "c-corner-conference-2017.html",
    "title": "C# Corner Annual Conference 2017 - Get Blogged by JoKi",
    "summary": "C# Corner Annual Conference 2017 Copy Markdown View Markdown GitHub Markdown Source C# Corner Annual Conference 2017 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'c-corner-conference-2017'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'c-corner-conference-2017'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'C# Corner Annual Conference 2017') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-05-30 - Filed under CommunityPersonal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Recently I have been watching the travel documentary \"James May, our man in India\". One of the episodes, in particular #2, while he is visiting Delhi and Agra, it hit a chord in my memories. Time to write about my trip to India and speaking at the C# Corner Annual Conference 2017. C# Corner Annual Conference 2017, Delhi (the link might probably not work anymore) It all started during 2016 when the founder of C# Corner Mahesh Chand invited me to come to and speak at the conference in 2016 already. Unfortunately, it was a quite short-noticed call and I had bookings for business scheduled already. Then, about the end of December fellow MVP and MSCC community member Chervine Bhiwoo sent me an email to check my Inbox for a potential speaking engagement with C# Corner Conference 2017. And indeed in early January Praveen Kumar and I had some conversation about potential submissions. We quickly agreed on the title of the talk and the rest was all about travel preparations. Travelling to India Whether you are Mauritian or not like in my case, India seems to ask quite a number of citizens to apply for a VISA to enter the country. The application process can be initiated online easily. For the actual VISA I went to the High Commission in Port Louis to pay the fees, drop off my passport and collect it after a week or so. The officer at the High Commission was very forth coming and friendly. Overall a pleasant experience to obtain a VISA for India. Note: The fees for a VISA depend on your citizenship; Mauritians pay less than what I paid for. For the arrangements for air travel there are multiple options available. Among them are flights on Air Mauritius, Air India, Emirates and others. I do not recall whether there is a direct flight between Delhi and Mauritius though. Probably yes but limited to certain days of the week only. Given the marginal difference in total travel time inclusive stop-over and the comfort offered by the airline, I booked my trip on Emirates with the usual short stop in Dubai. Otherwise, it would have been a stop-over in Chennai or Bangalore. With the additional flexibility of multiple flights per day I opted for Emirates, again. And I even got a nice deal to upgrade to Business Class. First impressions After two unspectacular flights I arrived safely in Delhi during the afternoon. And thanks to the organised airport transfer it was a breeze to experience the traffic first-hand. Whew, I mean driving in Mauritius can be an adventure sometimes in certain areas but this topped everything. It is scary and fascinating at the same time, not sure whether I can describe it using words only. A three-lane road has five to six vehicles of any kind next to each other, ranging from bicycles to auto-rickshaw - aka tuk tuk) to busses and heavyload lorries. And everyone is honking. Thankfully Manish is used to all of this and we arrived at the hotel in adequate time just to discover another novelty for me. The hotel, or more likely any hotel, seems to be a miniature fortress I got the impression. Heavy fencing around the property, armed units with machine guns slung over their shoulder, car inspection with mirrors to check for explosives, X-ray security scanners for your luggage, and metal detectors for people. Never seen this before... Anyway, there was a heartwarming reception procedure while waiting for the check-in process to be completed. A bit exhausted I happily reached the hotel room to stretch out and relax a bit. My community fellow and room-mate Chervine arrived a day earlier. Later this evening, we met with Dhananjay (DJ) Kumar and we talked a bit about Mauritius, India, developer communities in general, and all kind of other interesting topics. Totally unexpected and surprising, DJ gifted me the book \"The power of habit\" by Charles Duhigg. Very much appreciated. After a wonderful speakers dinner organised by C# Corner it was time to call it a day as there had been plans made for us the next day. This meant to catch some Zzzz's and then wake up earlier than usual. Road trip to Mathura Guess what? It's Uber time! First time, riding on an Uber... It was booked by our hosts in order to meet other speakers for today's trip to Mathura. Speaking in front of CS students at one of the local universities. Initially the Uber trip started quite good, the driver came to the hotel in time and we were off for a quick 30 minutes drive to join the others. And then he fell asleep on the wheel... After the second time grazing the wall in the middle of the highway, the traffic separation, we all urged the driver to stop the car instantly and we hauled another Uber to continue the trip safely. With quite some delay we met with a fellow from C# Corner and we were finally off to Mathura. Interestingly, the Yamuna Expressway passes along the Buddh International Circuit where the Indian Grand Prix in Formula One used to be. A track dominated by Sebastian Vettel during its short period on the F1 calendar. For the final stretch from the expressway to Mathura we drove on regular roads . We even had to take a diversion through the fields to reach the GLA University in Mathura. Joining the other speakers and checking out the agenda of the day there was little perparations for my talk on \"Advantages and Opportunities of Cross-Platform Development\". During my session I explained to the students present the different options and tools available to dive into cross-platform development. Afterwards, there had been quite a number of questions regarding Cordova, Xamarin and Electron which I gladly answered to my best knowledge. I wasn't aware about certain customs, the whole at the university we have been treated with utterly importance. I felt as some kind of celebrity; just showing up and taking the stage to talk. Positive although unusual impressions. The trip back to Delhi got us into a bit of rush hour. I mean, it's not like there's no traffic all day long but around early evening it seems to intensify by a magnitude or two. Mind-boggling driving experience! Compared to that, driving in Mauritius is like secured road education for young learners at kindergarten. And I figured that the constant honking is just a way to say 'Hello, I'm doing great and I'm an active participant on the road'. Nothing special about that. We rounded up the day with a speakers dinner back at the hotel. Some of the seasoned speakers shared their experience on delivering a great talk and offered their valuable advice to others. Again, it was a very pleasant activity and well received. The conference Despite my session being scheduled during the early afternoon, I woke up quite early to seize the opportunity to explore the conference venue. There were multiple rooms for the tracks and a large exhibition area for vendors and partners of the event. Big style stuff and very much like any of the European conferences I attended in the past. The registration desks were already buzzing with people. Everyone being really excited about the big day. As mentioned, there were multiple tracks in parallel and my session had been assigned to Track 1 which was occupying the main hall with the largest amount of seats. Whew, no pressure \uD83D\uDE09 Throughout the day it became appeared that we experienced an increasing delay regarding the starting times of the sessions. Well, nothing to worry about in India, and I have to admit that I was glad for some extra time to prepare myself for the talk. Quick hardware check and also verifying that all code samples and demos were working as expected and I was ready to rumble. It didn't go as smoothly as I hoped for but I had some source code to show on how to develop .NET Core applications on Linux. Back in the days I used Visual Studio Code and the dotnet CLI to convey the message to the audience. Afterwards, I had some great conversation with Joseph Guadagno about the Microsoft MVP program in general, and we cross-checked our list of MVP contacts. It was pretty amusing to exchange stories regarding some of the folks we both know. Later on, I chit-chatted a bit with Magnus Mårtensson, David McCarter, Pinal Dave, and a couple more speakers about their activities related to .NET, SQL Server and of course the C# Corner community. The had been numerous nuggets of great advice for growing our community in Mauritius and I got some ideas for the annual Developers Conference in Mauritius, too. Later that evening, all speakers and helpers of the conference were invited for a treat: Speakers dinner at a rooftop club in one of the suburbs of Delhi. Great fun I can tell you. And very interesting conversations, lots of jokes and laughter, and finally some great snacks to enjoy. Dunno when but it must have been after midnight that I dropped dead into my bed. Agra A day to relax and go sight-seeing. Chervine came up with the idea of visiting places. First and foremost, the Taj Mahal. Exploring Delhi Last day in India for this trip and my flight is early in the evening. Hence, we decided to explore the city of Delhi. After a savvy breakfast Chervine and I were Personal thoughts It's quite difficult to express my experience and impressions of India, in particular due to the very short stay, into the right words. Let me say it like this: It's overwhelming - in any sense and direction! The first thing that hit me was the incredible amount of people nearly everywhere. Then there is the adventure of road traffic. Frankly, I wouldn't dare to drive around Delhi myself. Not because I don't know how to drive but probably regarding the level of interactions, the level of concentration, and the uncommon levels of sound (honk honk). It's too crowded, perhaps chaotic at certain stages but that's the beauty of it I would say. Those few days in the state of Uttar Pradesh (UP) were really amazing and I had a great time mingling with other speakers and attendees of the C# Corner Annual Conference. The trips to Mathura and Agra gave me a small glimpse of the beauty of this humongous country called India. Walking through one of the many markets in Delhi and exploring the small shops surrounding it was mesmerising. Seeing the India Gate, the triumphal arch in Delhi, as well as Rashtrapati Bhavan with large park area was a nice treat at the end of my visit. I'm hoping that this isn't my last trip to India. There are surely other cities like Darjeeling, Chennai, Bangalore, Mumbai, etc. worth a visit. Let's see what the future holds. My thanks and gratitude to the community fellows at C# Corner. Keep shining! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'c-corner-conference-2017.html'; this.page.identifier = '9271ed1c97_idc-corner-conference-2017'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "c-essentials-2nd-edition.html": {
    "href": "c-essentials-2nd-edition.html",
    "title": "C# Essentials, 2nd Edition - Get Blogged by JoKi",
    "summary": "C# Essentials, 2nd Edition Copy Markdown View Markdown GitHub Markdown Source C# Essentials, 2nd Edition Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'c-essentials-2nd-edition'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'c-essentials-2nd-edition'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'C# Essentials, 2nd Edition') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-16 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ein weiteres Buch ist verzehrt... Well, wo fange ich an? Hm, vielleicht einfach mal mit dem 'Warum'. Und dann sehen wir mal, was auf der Tastatur zustande kommt. C# Essentials, 2nd Edition Ich habe mir das Buch besorgt, um einen schnellen und knackigen Überblick über die neue Programmiersprache C# zu erhalten, ohne dass ich einen Sackkarren für unterwegs benötige. Und genau das verheißt auch die Rückseite des Bucheinbands. Auf knapp 200 Seiten vermitteln die Autoren die Grundbausteine des .NET Frameworks, durchlaufen in geschickter Abfolge die sprachlichen Elemente von C#, und zeigen mit verständlichen Beispielen wie die verschiedenen Teile zusammengesetzt werden. Das zweite Kapitel erklärt die essentiellen Bestandteile von C#. Die Autoren konzentrieren sich auf den Punkt des Themas ohne allzu sehr in die Ferne zu schweifen oder mit aufgeblähten Satzkonstrukten den Leser zu verwirren. Außerdem bauen die Abschnitte inhaltlich sehr gut aufeinander auf. Dadurch hat man einen regelrechten Lesefluß, ohne durch Sprünge ins Stocken zu geraten. Dieser Umstand definiert die Zielgruppe an Lesern - erfahrenere Entwickler, die in kurzer Zeit das Fundament der Sprache C# erfassen wollen. Um den Bogen zu meinem ersten Satz dieser Rezension zu spannen: Ja, das Buch hat meine Erwartungen und Vorstellungen vollständig erfüllt. Vor allem auch durch die Tatsache, dass nicht spezifisch auf irgendwelche Produkte eingegangen wurde, sondern nur C#. Ich bin mir jetzt schon sicher, dass ich öfters in diesem Buch Informationen nachschlagen werde. Mein Dank gilt den Autoren und O'Reilly für dieses Schmuckstück. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'c-essentials-2nd-edition.html'; this.page.identifier = '9271ed1c97_idc-essentials-2nd-edition'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "c-unter-linux.html": {
    "href": "c-unter-linux.html",
    "title": "C# unter Linux - Get Blogged by JoKi",
    "summary": "C# unter Linux Copy Markdown View Markdown GitHub Markdown Source C# unter Linux Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'c-unter-linux'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'c-unter-linux'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'C# unter Linux') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-12-01 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Warum eigentlich nur FoxPro programmieren? Genau diese Frage habe ich mir vor einigen Wochen auch schon gestellt, und daher bin ich derzeit auf neuen Pfaden unterwegs... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'c-unter-linux.html'; this.page.identifier = '9271ed1c97_idc-unter-linux'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "celebrating-earth-hour-2013.html": {
    "href": "celebrating-earth-hour-2013.html",
    "title": "Celebrating Earth Hour 2013 - Get Blogged by JoKi",
    "summary": "Celebrating Earth Hour 2013 Copy Markdown View Markdown GitHub Markdown Source Celebrating Earth Hour 2013 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'celebrating-earth-hour-2013'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'celebrating-earth-hour-2013'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Celebrating Earth Hour 2013') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-03-23 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Once again we are going to celebrate this year's Earth Hour which is going to happen today! So, what is it about after all? Well, the challenge in earth hour is to switch off any kind of electrical devices for at least one hour in order to honour nature and Mother Earth. It starts from disconnecting from your online activities on Facebook, Google+ or Twitter, by switching off your lights in general and could beyond turning off your electrical master switch and disconnect from the power grid. Here's the official statement from Earth Hour's website: \"Earth Hour is the single, largest, symbolic mass participation event in the world. Born out of a hope that we could mobilize people to take action on climate change, Earth Hour now inspires a global community of millions of people in 7,001 cities and towns across 152 countries and territories to switch lights off for an hour as a massive show of concern for the environment.\" Our plans for tonight... The Earth Hour in Mauritius is going to happen between 8:30pm and 9:30pm, and we are going to have a barbeque in candle lights with close friends and family. As Easter is also approaching, we might check out some treasure hunt for the children. Honestly, it's going to be another great 'off-line' experience for all of us. Regular Earth Hour in Mauritius - anyways! \uD83D\uDE09 Taking the number of irregular power cuts we are experiencing through out the year, I would simply say that in general it isn't necessary for Mauritians to explicitly celebrate Earth Hour. But of course, it's about the world-wide movement and spirit to participate and show your respect to nature. Talking about Mauritius, the website of Earth Hour also lists countries where regional or local activists come together and organise themselves for this special occassion. Again, another great opportunity to meet great and passionate people in your neighbourhood. Eventually, you might like to check out Earth Mauritius - even though their website is too \"flashy\" for my taste. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'celebrating-earth-hour-2013.html'; this.page.identifier = '9271ed1c97_idcelebrating-earth-hour-2013'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "children-happy-kindle-fire-hdx.html": {
    "href": "children-happy-kindle-fire-hdx.html",
    "title": "Children are happy using their Kindle Fire HDX - Get Blogged by JoKi",
    "summary": "Children are happy using their Kindle Fire HDX Copy Markdown View Markdown GitHub Markdown Source Children are happy using their Kindle Fire HDX Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'children-happy-kindle-fire-hdx'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'children-happy-kindle-fire-hdx'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Children are happy using their Kindle Fire HDX') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-01-04 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Source: Kindle Fire HDX 7\" Tablet on Amazon Another bargain from Amazon made it to our paradise island. This time I went on the hunt for tablet for our children. Even though I already use a variety of tablets - both iOS- and Android-based - for professional reasons I was looking for a more secure solution for children than what is possible with the default systems. Whether you are using an iPad or a stock Android or a Samsung enhanced UI tablet they somehow always fall short in terms of profile management for multiple users, there is lack of safety measures for children, and it surely leaves a gut feeling with the adults when the kids are using \"their\" device. Some months back I ran into the unpleasant situation that one of our children did an unwanted purchase on the Play Store. Fortunately for me, Google changed their refund policies recently and I was granted a refund. Grateful, it happened only once and just a minor amount. Due to this incident I did a research on several \"child-friendly parachutes\" for Android. None of them really convinced and so I put it aside... Amazon again... Then came Amazon and only by chance while browsing some products in their Kindle department I came across their special offers for the Kindle Fire HDX tablet(s). Reading through the family features on the device combined with the additional cloud options it became obvious that the Kindle Fire would be wonderful solution. Here are some of the key factors - taken from Amazon product page - that mattered most for us: Profiles so you can share your tablet Kindle Fire HDX (OS 4) lets each family member create their own profile, including individual home screens, email, Facebook and Twitter accounts, bookmarks, and game levels—making it easy for a family to share a tablet and give everyone a personalized experience, rather than buying multiple devices. Set Screen Limits Many parents choose to limit their kids' screen time, but doing so without the proper tools is difficult. With Kindle FreeTime, parents can set daily limits for tablet use, or restrict certain categories—like games and video—while leaving unlimited time for reading. Perfect for Parents... Kindle FreeTime is built from the ground up to help give parents peace of mind when it comes to tablet use. Parents can create a profile for each of their children and choose what books, apps, games, and videos they want to give their kids access to. Kindle FreeTime is free on every new Kindle Fire. ...And Perfect for Kids When kids use Kindle FreeTime, it's like they have their very own personalized tablet. The background color and fonts change to a kid-friendly design, they only see the titles that they have access to see, the home screen carousel shows their recently viewed titles, and they can even navigate visually to content based on characters or topics—for example \"Cinderella,\" \"Dinosaurs,\" or \"Puppies.\" Just the combination that we were looking for. Profile creation and protection The Fire OS 4 used in the Kindle Fire HDX allows to setup a family with 2 adults and up to 4 children. Being used to the Amazon Store App on my other devices and already owning a small library of Android apps as well as a collection of (children) books the Kindle Fire would be the right selection for our household. Actually, our toughest decision was whether we should take a rugged version or stick to the Origami cover... Tough decision between the Fire HD Kids Edition (left) and the Fire HDX with Origami Cover (right)... In general, I would suggest that someone might opt for the Fire HD Kids Edition depending on the age your child(ren) of course. But honestly I have to admit that the technical geek in me won the decision battle and we purchased the regular HDX with higher display resolution. Although the offer with 1 year of Amazon FreeTime Unlimited, and a 2-year worry-free guarantee sounds amazing. Well, personal preference... Initial configuration This is pretty much a standard procedure. Our device(s) came almost fully charged and therefore it was about the first log in with the credentials of an adult Amazon account holder. Following the various steps took me less than 10 minutes and the device was operational. Of course, first things first... Check for updates! And yes there is a newer minor version of Fire OS available. Some bugfixes but also some new and enhanced features compared to the currently installed version. Next, it's about going into the Settings menu and there you get access to Profiles & Family Library. That's the place to go to... I quickly added two child profiles and made my choices regarding allowed content for their particular age. It's easy to provide different books, apps, games, videos and settings for each individual profile. And more interesting you are able to set daily goals but also set time limits and usage constraints. Optionally you can give profile passwords or even hide profiles completely on the device - nice feature to keep the kids restricted to a) their profile and b) their device. Overall I have to say that Amazon's Fire OS experience is more complete and polished than any children-safety app that I have tested since now. Content and battery lifetime The children got their tablets as present from Santa Claus... And of course, he is very gentle person he already pre-loaded a good number of apps from the Amazon App Store. I'm not quite sure but it seems that each app is downloaded for each profile. Leading to duplicate downloads and higher storage requirements. But I'm not sure about that yet. The Kindle Fire HDX is completely isolated within the Amazon realm, and you don't have access to Google's Play Store or any other sources - which might be good or might be bad depending on how you would like to see it. Personally, I don't miss the access to the Play Store and again it gives me more peace of mind knowing that the kids cannot download and install any inappropriate software. Regarding battery lifetime it seems that the tablet has enough \"juice\" for their daily allowance without the need to charge it in-between. Well, I have to admit that I didn't load any movies yet on the device. But up till now, the kids are happy with their playtime using games, drawing apps, reading children books or listening to music. Camera situation The Kindle Fire HDX is \"only\" equipped with a front-facing 720p HD camera. Which is perfect for Skype conversations among each other, or family members, or friends outside our immediate vicinity. And of course \"selfies\"... Hm, our little artists discovered this drawback during the first evening and were not quite happy about that. As we didn't comment on this they found a solution on their own: Turn the tablet and hold in front of the belly to shoot photos. Simply but effective. Resume Even after this short period of less than two weeks I'd say that we made the right decision with the purchase of the Kindle Fire HDX and the Origami cover. The children are pleased with their toys, and we as parents can relax - they don't ask anymore for our devices and we feel secure that they won't do any unwanted purchases. Feel free to leave me a note (or two) in the comment section below. Thanks! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'children-happy-kindle-fire-hdx.html'; this.page.identifier = '9271ed1c97_idchildren-happy-kindle-fire-hdx'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "clash-of-the-browsers.html": {
    "href": "clash-of-the-browsers.html",
    "title": "Clash of the browsers - Get Blogged by JoKi",
    "summary": "Clash of the browsers Copy Markdown View Markdown GitHub Markdown Source Clash of the browsers Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'clash-of-the-browsers'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'clash-of-the-browsers'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Clash of the browsers') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-03-13 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es ist immer wieder ernüchternd, wenn man mit JavaScript das magere HTML-Frontend aufpeppen möchte. Nunja, heute ging's um Resizing von Frames... Wie Frames? - Ja, ich kann die Teile überhaupt nicht abhaben, aber manchmal geht's dann halt doch nicht anders. Und genau so einen Fall habe ich gerade (oder immer noch). Okay, die optische Politur muss her, denn eine Verschmelzung zwischen Active FoxPro Pages und PHP soll zumindest auf den ersten Blick einigermaßen unsichtbar sein bzw. bleiben. Nein, nicht um AFP zu pushen, sondern ausschliesslich der Usability und Optik wegen. Zudem sind IFrames für solch einen Zweck einigermaßen zu gebrauchen. Well, was gilt es zu beachten? - zunächst einmal, dass potentiell der gewünschte Content erscheint. Dann gestaltet es sich weiterhin als interessant, dass der IFrame gefälligst ohne Scrollbar angezeigt wird... Und hier beginnen die Probleme. Die Breite ist noch einigermaßen easy zu kontrollieren, aber die Höhe des IFrame; da wird's schon ein klein wenig schwieriger. Oder zumindest kommt es mir so vor. Nun denn. Basierend auf einer interessanten Vorlage habe ich mich wieder in die Tiefen von JavaScript und Browser DOMs getraut, und wie zu erwarten auch gleich mal wieder einen auf den Deckel bekommen. Hier mal den aktuellen Code der Implementierung, damit klarer wird, wovon ich überhaupt spreche: function resizeFrame(objectID) { var e; var domFrame = findDOM(objectID); var domFrameStyle = findDOM(objectID,1); var domFrameDoc = (window.navigator.appName.toLowerCase().indexOf(\"netscape\") > -1) ? domFrame.contentWindow.document : domFrame.document var fheight = 0; try { // Get absolute height of document inside the frame. fheight = domFrameDoc.body.scrollHeight; } catch(e) { window.status = \"Exception: \" + e.description; fheight = 1000; } domFrame.height = fheight; domFrameStyle.display = 'block'; } Spannenderweise muss mit einem Try-Catch gearbeitet werden, da FireFox den Zugriff mit einer Exception quittiert. Und darin liegt auch die Sache ein wenig begraben. Ich hab's ebenfalls mittels Statusabfrage von document.readyState probiert, aber auch dann sieht's nicht besser aus. More to come... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'clash-of-the-browsers.html'; this.page.identifier = '9271ed1c97_idclash-of-the-browsers'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "comment-on-competent-mauritius.html": {
    "href": "comment-on-competent-mauritius.html",
    "title": "Comment on 'Competent Mauritius' - Get Blogged by JoKi",
    "summary": "Comment on 'Competent Mauritius' Copy Markdown View Markdown GitHub Markdown Source Comment on 'Competent Mauritius' Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'comment-on-competent-mauritius'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'comment-on-competent-mauritius'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Comment on 'Competent Mauritius'') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-04-02 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Sadly, my comment on the original address is not available. Might have been due to technical issues, don't know. Following is a referral to this source: https://www.noulakaz.net/weblog/2013/03/31/competent-mauritius/ Hi Avinash, Well, this topic is really interesting, and I spoke about that on several occasions with friends and family. Honestly, there are so many topics that leaves me only by shaking my head or giving face palms to the responsible bodies. But one thing for sure that I discovered is simply the fact that it isn't about competency at all. It's simply about \"copinage\" and putting money in your pockets. Just to mention a few topics: Cyber Island: Since years simply a joke and recent article in the news confirms it. Rank #131 out of 183 countries. Ile Durable: Very hot topic as it involves everyone. There is absolutely no consciousness in the population to handle water, energy and fuel properly. It starts in every single household by educating the children to throw their rubbish into the bin instead of on the road or the drains. The availability of solar energy panels is almost zero, or extremely expensive. There are no rain water collection tanks. If you have a look at other island nations, like ie. Bahamas it is compulsory that every house collects rain water. Ask anyone on the street about what recycling is... You'll be amazed by the answers! 'Mauritius, c'est un plaisir': After spending millions of Rupees into the PM's company in UK, that slogan was silently changed into 'Simply divine' to avoid to build up an image of loathing and entertainment. Airport Terminal: Actually, I'm not referring to Nandanee but the initial phase where the construction company was literally pushed to 'import' Chinese labourers as Mauritian fellows were either not capable or simply unreliable to do the job. IIRC, we are talking about approx. 6000 positions in craftsmanship. Especially in the south with lowest level of infrastructure and highest level of poverty in this very country. Motorway M3: Tunnel or Flyover to bypass Port Louis? What a question! Well, now that various phases are done, when is the tunnel going to be ready. At the current state, it will take at least another 2 years until completion. Drought: Just last week I saw a very interesting documentary about the sugar cane industry on Hawaii. I would say almost 100% identical climate and infrastructural problems. They came up with an ingenious solution and constructed an artificial drain system to connect the rainy mountain slopes to the dry plains on the other side of the mountains. It's a small tunnel system of more than 25km. Knowing the fact that most of the rain does not find it's way into the Mauritian reservoirs. Why not channel the rain water? gov.mu: 50m for a web site? Simply outrageous and it falls into the category of putting money in your own pocket. And the list goes on and on and on... Don't get me wrong anyone, I simply love this island and even though that I am not a native I suffer watching, reading about or listening to quite a lot of the BS that is coming from those 'competent' public bodies. Especially, when someone blames global warming as reason for the flooding incidents in February and March. As if Mauritius doesn't know how to deal with cyclones anymore... And guys, please leave the MET in peace. Weather is hard to predict and even that they warned about torrential rain falls, it is the local conditions of the terrain that caused the problems. Since the construction works and enlargement of the motorway the problems started. Or did we have this kind of flooding during the past years, even during cyclones? Well, at least not that I am aware of... Just my two cents, JoKi Tell me, what's your opinion on the aftermath of 30th March 2013? if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'comment-on-competent-mauritius.html'; this.page.identifier = '9271ed1c97_idcomment-on-competent-mauritius'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "community-community-community.html": {
    "href": "community-community-community.html",
    "title": "Community, Community, Community! - Get Blogged by JoKi",
    "summary": "Community, Community, Community! Copy Markdown View Markdown GitHub Markdown Source Community, Community, Community! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-community-community'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-community-community'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Community, Community, Community!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-11 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Heute ist wieder volles Programm angesagt. Der Start heißt Schnee. Dann Frühstück und ab die Waldfee an den Lister Turm, da wir dort heute Abend den Tag mit dem CeBIT VFP Stammtisch abschliessen werden. Jaja, wir sind keine kleine Dummen, sondern nehmen die S-Bahn zum Messegelände. Alles locker und easy. Zweites Frühstück im Kreise der bekannten CLIP-Mitglieder und MVPs. Kann ein Community-Tag besser starten? Eigentlich nicht, oder? Und allmählich trudeln die 'Üblichen Verdächtigen' in die Seminarräume ein. Hallo hier, Shakehands dort - cool! Pünktlich steigen wir die Agenda 3.0 ein. Hehehe, nach der ergänzten Fassung per Mail gab's heute morgen direkt die druckfrische Variante, da unter anderem ein Office 2007 Entwickler aus Redmond sich spontan und enthusiatisch für eine kurze Session zu den UI-Verbesserungen in Office 2007 freiwillig gemeldet hat. Okay, wir sind gespannt. Really! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'community-community-community.html'; this.page.identifier = '9271ed1c97_idcommunity-community-community'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "community-day-at-sos-kinderdorf-bambous.html": {
    "href": "community-day-at-sos-kinderdorf-bambous.html",
    "title": "Community Day at SOS Kinderdorf Bambous - Get Blogged by JoKi",
    "summary": "Community Day at SOS Kinderdorf Bambous Copy Markdown View Markdown GitHub Markdown Source Community Day at SOS Kinderdorf Bambous Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-day-at-sos-kinderdorf-bambous'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-day-at-sos-kinderdorf-bambous'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Community Day at SOS Kinderdorf Bambous') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-04-09 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Easter is not only an occassion of public holidays but an opportunity to share and celebrate with your neighborhood. The team of IOS Indian Ocean Software Ltd. spent Easter Monday with the children and \"mothers\" of SOS Children's Village in Bambous. Together with the employees' family members we enjoyed some wonderful hours together. Starting the community day with a common lunch, IOS contributed mainly stationery, educational gifts and games like Scrabble or Sudoku. And of course, a little give-away with chocolate eggs... The kids are aged between 5 and 18 years. They live together with their village mothers in so-called family houses. The SOS Kinderdorf in Bambous is quite new and started their operations back in 2003, with an official inauguration in May 2005. Actually, I'm really happy about this event and I am looking forward to IOS next community activity. Community off-line and for real people, not only virtual! PS: Article in L'Express of 09.04.2010 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'community-day-at-sos-kinderdorf-bambous.html'; this.page.identifier = '9271ed1c97_idcommunity-day-at-sos-kinderdorf-bambous'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "community-devcon-2005.html": {
    "href": "community-devcon-2005.html",
    "title": "Community DevCon 2005 - Get Blogged by JoKi",
    "summary": "Community DevCon 2005 Copy Markdown View Markdown GitHub Markdown Source Community DevCon 2005 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-devcon-2005'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-devcon-2005'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Community DevCon 2005') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-19 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Endlich! Die Einladung kam bereits im Dezember - noch vor Weihnachten - und bis heute zieht sich die Zeit doch wie ein Kaugummi. Aber endlich ist wieder Microsoft Coummnity DevCon! Yeah, allein die Aussicht, wieder andere CLIP Mitglieder, MVPs und INETA Mitglieder offline zu treffen und Gespräche zu führen, erfreut mich. Ich find's wirklich klasse. Und so auch wieder heute. Microsoft lud dieses Jahr nach Frankfurt/Main ein. Ein Katzensprung für uns - nur 1 Stunde Autofahrt, wenn keine Staus sind... Leider gibt's aber motorisierte Verkehrsteilnehmer, denen offensichtlich nicht klar ist, wieso es Abbiegespuren gibt. Apropos Katzensprung bzw. vergleichbar. Auf der Hinfahrt hatten wir wiederum einen Fuchs ausmachen können - ein Zeichen? Nunja, wir haben es dennoch rechtzeitig geschafft. \uD83D\uDE01 Die vermittelten Infos zu Team System als umfassendes Entwicklungssystem im kommenden Visual Studio 2005, sowie die Backgrounds zu den Microsoft Hellhounds - übrigens amtierende Weltmeister im Robotfussball - waren wirklich sehr aufschlussreich wie auch amüsant, da Ingo die erste Zeit ohne Beamer auskommen musste. Ohne Strom geht's halt nicht... \uD83E\uDD23 Eine Änderung gab's dieses Jahr im Konzept der DevCon, denn sie bestand nicht nur aus Vorträgen, sondern positiver Weise wurden nach dem Mittagessen kleinere Workshops (aka Breakouts) für die drei Coummnity-Aktivitäten - CLIP/MVP, INETA und CodeZone - gebildet. Dabei wurden aktuelle Problemchen, Kritiken, Wünsche und Aussichten in Zusammenarbeit mit Microsoft angegangen. Interessanterweise floßen sehr viele unterschiedliche Erfahrungen der Usergroup-Leader ein. Networking live! Klasse! Kleiner Gag am Rande: Die .NET Usergroup Paderborn wurde während des Breakouts mittels Messenger initialisiert - Gratulation! Abschliessend wurden gemeinsam die ausgearbeiteten Ergebnisse untereinander vorgestellt und roughly definiert, wie die weiteren Schritte - bis zur CeBIT und später - aussehen werden. Aber dann DAS Highlight des Tages - die Vorstellung von Visual FoxPro. Im Schnelldurchlauf gab Rainer einen starken Überblick über die Programmiersprache mit Datenbankengine und Reportgenerator. Was einiges Murmeln, ob der Features brachte. Ich glaube, es war auch wichtig, dass VFP anderen mal vorgestellt wurde, denn \"Was machen die VFP-Jungs eigentlich überhaupt und hier?\" dürfte eine häufigere Frage bei anderen Community-Mitgliedern gewesen sein. Oder? \uD83D\uDE09 Okay, ich freu' mich bereits jetzt schon auf das nächste Treffen auf der CeBIT. Leider gibt's dabei für mich einen kleinen Konflikt: Am Freitag ist sowohl VFP CeBIT Stammtisch als auch Microsoft Einladung angesagt. Wir werden sehen... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'community-devcon-2005.html'; this.page.identifier = '9271ed1c97_idcommunity-devcon-2005'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "community-exchange-and-discussion.html": {
    "href": "community-exchange-and-discussion.html",
    "title": "Communities - The importance of exchange and discussion - Get Blogged by JoKi",
    "summary": "Communities - The importance of exchange and discussion Copy Markdown View Markdown GitHub Markdown Source Communities - The importance of exchange and discussion Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-exchange-and-discussion'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community-exchange-and-discussion'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Communities - The importance of exchange and discussion') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-05-29 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Communication with your environment is an essential part of everyone's life. And it doesn't matter whether you are actually living in a rural area in the middle of nowhere, within the pulsating heart of a big city, or in my case on a wonderful island in the Indian Ocean. The ability to exchange your thoughts, your experience and your worries with another person helps you to get different points of view and new ideas on how to resolve an issue you might be confronted with. Benefits of community work What happens to be common sense in your daily life, also applies to your work environment. Working in IT, or ICT as it is called in Mauritius, requires a lot of reading and learning. Not only during your lectures at the university but with your colleagues in a project assignment and hopefully with 'unknown' pals in the universe of online communities. At least I can say that I learned quite a lot from other developers code, their responses in various forums, their numerous blog articles, and while attending local user group meetings. When I started to work as a professional software developer (or engineer some may say) years ago I immediately checked the existence of communities on the programming language, the database technology and other vital information on software development in general. Luckily, it wasn't too difficult to find. My employer had a subscription of the monthly magazines and newsletters of a national organisation which also run the biggest forum in that area. Getting in touch with other developers and reading their common problems but also solutions was a huge benefit to my growth. Image courtesy of Michael Kappel (CC BY-NC 2.0) Active participation and regular contribution to this community gave me some nice advantages, too. Within three years I was listed as a conference speaker at the annual developer's conference and provided several sessions on different topics during consecutive years. Back in 2004, I took over the responsibility and management of the monthly meetings of a regional user group, and organised it for more than two years. Furthermore, I was invited to the newly-founded community program of Microsoft Germany (Community Leader/Insider Program - CLIP). My website on Active FoxPro Pages was nominated in the second batch of online communities. Due to my community work and providing advice to others, I had the honour to be awarded as Microsoft Most Valuable Professional (MVP) - Visual Developer for Visual FoxPro in the years 2006 and 2007. It was a great experience to meet with other like-minded people and I'm really grateful for that. Just in case, more details are listed in my Curriculum Vitae. But this all changed when I moved to Mauritius... Cyber island Mauritius? During the first months in Mauritius I was way too busy to think about community activities at all. First of all, there was the new company that had to be set up, the new staff had to be trained and of course the communication work-flows and so on with the project managers back in Germany had to be sorted out, too. Second, I had to get a grip of my private matters like getting the basics for my new household or exploring the neighbourhood, and last but not least I needed a break from the hectic and intensive work prior to my departure. As soon as the sea literally calmed down, I started to have conversations with my colleagues about communities and user groups. Sadly, it turned out that there were none, or at least no one was aware of any at that time. Oh oh, what did I do? Anyway, having this kind of background and very positive experience with off-line and on-line activities I decided for myself that some day I'm going to found a community in Mauritius for all kind of IT/ICT-related fields. The main focus might be on software development but not on a certain technology or methodology. It was clear to me that it should be an open infrastructure and anyone is welcome to join, to experience, to share and to contribute if they would like to. That was the idea at that time... Ok, fast-forward to recent events. At the end of October 2012 I was invited to an event called Open Days organised by Microsoft Indian Ocean Islands together with other local partners and resellers. There I got in touch with local Technical Evangelist Arnaud Meslier and we had a good conversation on communities during the breaks. Eventually, I left a good impression on him, as we are having chats on Facebook or Skype irregularly. Well, seeing that my personal and professional surroundings have been settled and running smooth, having that great exchange and contact with Microsoft IOI (again), and being really eager to re-animate my intentions from 2007, I recently founded a new community: Mauritius Software Craftsmanship Community - #MSCC It took me a while to settle down with the name but it was obvious that the community should not be attached to one single technology, like ie. .NET user group, Oracle developers, or Joomla friends (these are fictitious names). There are several other reasons why I came up with 'Craftsmanship' as the core topic of this community. The expression of 'engineering' didn't feel right with the fields covered. Software development in all kind of facets is a craft, and therefore demands a lot of practice but also guidance from more experienced developers. It also includes the process of designing, modelling and drafting the ideas. Has to deal with various types of tests and test methodologies, and of course should be focused on flexible and agile ways of acting. In order to meet and to excel a customer's request for a solution. Next, I was looking for an easy way to handle the organisation of events and meeting appointments. Using all kind of social media platforms like Google+, LinkedIn, Facebook, Xing, etc. I was never really confident about their features of event handling. More by chance I stumbled upon Meetup.com and in combination with the other entities (G+ Communities, FB Pages or in Groups) I am looking forward to advertise and manage all future activities here: Mauritius Software Craftsmanship Community This is a community for those who care and are proud of what they do. For those developers, regardless how experienced they are, who want to improve and master their craft. This is a community for those who believe that being average is just not good enough. I know, there are not many 'craftsmen' yet but it's a start... Let's see how it looks like by the end of the year. There are free smartphone apps for Android and iOS from Meetup.com that allow you to keep track of meetings and to stay informed on latest updates. And last but not least, there will be a Trello workspace to collect and share ideas and provide downloads of slides, etc. Sharing is caring! As mentioned, the #MSCC is present in various social media networks in order to cover as many people as possible here in Mauritius. Following is an overview of the current networks: Twitter - Latest updates and quickies Google+ - Community channel Facebook - Community Page LinkedIn - Community Group Trello - Collaboration workspace to share and develop ideas Hopefully, this covers the majority of computer-related people in Mauritius. Please spread the word about the #MSCC between your colleagues, your friends and other interested 'geeks'. Your future looks bright Running and participating in a user group or any kind of community usually provides quite a number of advantages for anyone. On the one side it is very joyful for me to organise appointments and get in touch with people that might be interested to present a little demo of their projects or their recent problems they had to tackle down, and on the other side there are lots of companies that have various support programs or sponsorships especially tailored for user groups. At the moment, I already have a couple of gimmicks that I would like to hand out in small contests or raffles during one of the upcoming meetings, and as said, companies provide all kind of goodies, books free of charge, or sometimes even licenses for communities. Meeting other software developers or IT guys also opens up your point of view on the local market and there might be interesting projects or job offers available, too. A community like the Mauritius Software Craftsmanship Community is great for freelancers, self-employed, students and of course employees. Meetings will be organised on a regular basis, and I'm open to all kind of suggestions from you. Please leave a comment here in blog or join the conversations in the above mentioned social networks. Let's get this community up and running, my fellow Mauritians! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'community-exchange-and-discussion.html'; this.page.identifier = '9271ed1c97_idcommunity-exchange-and-discussion'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "community.html": {
    "href": "community.html",
    "title": "Community - Get Blogged by JoKi",
    "summary": "Community Copy Markdown View Markdown GitHub Markdown Source Community Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'community'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Community') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); Sharing is Caring! Networking & Community Communication with your environment is an essential part of everyone's life. And it doesn't matter whether you are actually living in a rural area in the middle of nowhere, within the pulsating heart of a big city, or in my case on a wonderful island in the Indian Ocean. The ability to exchange your thoughts, your experience and your worries with another person helps you to get different points of view and new ideas on how to resolve an issue you might be confronted with. Take the chance to exchange with other attendees in the reception area or on the balcony. Have some interesting conversations about software development, latest information gained during one of the presentations and let others know about your ups & downs in your projects. Mauritius Software Craftsmanship Community The MSCC is technology-agnostic community and spans an umbrella over any kind of technology. Simply because you can't ignore other technologies anymore in a connected IT world as we have. A community for those who care and are proud of what they do. For those developers, regardless how experienced they are, who want to improve and master their craft. This is a community for those who believe that being average is just not good enough. Get more information in the Introduction to Mauritius Software Craftsmanship Community. GDG Mauritius The local GDG chapter also organises (ir-)regular events which are focused on anything Google technology. Most noticeable would be Google Cloud, Angular, Flutter, but also other widely used tech stacks. Most noteably, GDG Mauritius participates in global event series, like Build with AI, Google I/O Extended and DevFest among others. Head over to their community page at https://gdg.community.dev/gdg-mauritius/ for more information and registration. GDG Mauritius is an independent group; their activities and the opinions expressed should in no way be linked to Google, the corporation. Linux User Group Mauritius The Linux User Group of Mauritius is one of the few Linux User Groups in the Republic of Mauritius, quite possibly the only active one. We are registered with the Registrar of Associations under the name \"Linux User Group Meta\" since 2009. We all share a common interest, Linux, GNU and Free Software. The LUGM is open to all those in Mauritius who are curious about Linux, users of Linux (new or seasoned) and lovers of free software/open source in general. Frontend Coders Mauritius https://coders.mu/ has evolved into a multi-disciplinary community. Our mission is simple: to foster a vibrant developer community in Mauritius by providing platforms for learning, networking, and high-impact collaboration. PHP Mauritius User Group #phpMauritiusUG is the first PHP user group in Mauritius. It was founded by two passionate PHPer in 2016 during #DevConMRU 2016, for Mauritius by 100% Mauritians. We believe strongly in The PHP Community and we are honored and humble to be directly connected with them. The PHP Mauritius UG aims at shaping a community of people in Mauritius who are really passionate about #PHP & specially The PHP Community. Thus doing, we want to raise the level of PHP skills in Mauritius and help each and everyone that are keen to using & talking about PHP. Mauritius Maker Community The Mauritius Makers community is a group of enthusiasts and passionate people, who get their hands dirty by engineering things, enhancing existing things, finding solutions to problems etc. Although makers encompasses a diverse range of disciplines or community is geared primarily toward technological innovation. If you are interested in SBC, SoC, Arduino, ESP8266, Raspberry Pi, GPIO, Intel Edison, sensors, actuators, IoT it would be a pleasure to have you in our community. More user groups Since a few years the technology landscape in Mauritius has picked up some pace, and there are numerous other user groups that organise regular gatherings and activities."
  },
  "comproxy.html": {
    "href": "comproxy.html",
    "title": "COM Proxy for .NET - Get Blogged by JoKi",
    "summary": "COM Proxy for .NET Copy Markdown View Markdown GitHub Markdown Source COM Proxy for .NET Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'comproxy'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'comproxy'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'COM Proxy for .NET') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2009-12-14 - Filed under Projects (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); COM Proxy for .NET **Motivation ** Based on some research I did in december 2005 and currently refreshed due to a vital discussion in on of Microsoft's news groups at news://msnews.microsoft.com/microsoft.public.de.fox (German) about the future of Visual FoxPro, I decided to finalize and deploy my COM Proxy for .NET. As you might guess the name of this component is its main purpose. It is a dynamic-link library that provides features of the .NET framework classes via COM to any legacy programming language like Visual FoxPro. Contrary to current Sedna CTP this component has the same interface for Regular Expressions as the VBScript object has. So, this way it is a lot easier to switch between different implementations. Setup and Installation The component comes in two flavors - for .NET Framework 1.1 and 2.0. The setup routine installs both in separate folders and registers both in Windows. But no fear, the ProgID is the same for both, so it's again very handy for a VFP developer. Last registration of the component is the active one. I'd like to mention that the registration uses the /codebase switch of regasm tool and therefore creates no entries in to the Global Assembly Cache (GAC) on the machine. The current version of the setup is available at my German blog at the URL https://jochen.kirstaetter.name/files/ComProxyForNet.msi Just get the file and follow the installation instructions. Usage in Visual FoxPro So, while the components are COM-based dynamic-link libraries usage in Visual FoxPro is done with its CreateObject() function. The returned object reference is an instance of .NET's Regex class. RegEx = CreateObject(\"ProLib.RegEx\") ? RegEx.IsMatch(\"abc\",\"\\w\") You can use any option and functionality of System.Text.RegularExpressions.Regex in Visual FoxPro now. Further information and details on this namespace are provided in the MSDN if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'comproxy.html'; this.page.identifier = '9271ed1c97_idcomproxy'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "configure-ipv6-on-ubuntu.html": {
    "href": "configure-ipv6-on-ubuntu.html",
    "title": "Configure IPv6 on your Linux system (Ubuntu) - Get Blogged by JoKi",
    "summary": "Configure IPv6 on your Linux system (Ubuntu) Copy Markdown View Markdown GitHub Markdown Source Configure IPv6 on your Linux system (Ubuntu) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'configure-ipv6-on-ubuntu'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'configure-ipv6-on-ubuntu'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Configure IPv6 on your Linux system (Ubuntu)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-03-10 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After the presentation on IPv6 at the first event of the Emtel Knowledge Series and some recent discussion on social media networks with other geeks and Linux interested IT people here in Mauritius, I thought that I should give it a try (finally) and tweak my local network infrastructure. Honestly, I have been to busy with contractual project work and it never really occurred to me to set up IPv6 in my LAN. Well, the following paragraphs are going to shed some light on those aspects of modern computer and network technology. This is the first article in a series on IPv6 configuration: Configure IPv6 on your Linux system DHCPv6: Provide IPv6 information in your local network Enabling DNS for IPv6 infrastructure Accessing your web server via IPv6 Piece of advice: This is based on my findings on the internet while reading other people's helpful articles and going through a couple of man-pages on my local system. Let's embrace IPv6 The basic configuration on Linux is actually very simple as the kernel, operating system, and user-space programs support that protocol natively. If your system is ready to go for IP (aka: IPv4), then you are good to go for anything else. At least, I didn't have to install any additional packages on my system(s). We are going to assign a static IPv6 address to the system. Hence, we have to modify the definition of interfaces and check whether we have an inet6 entry specified. Open your favourite text editor and check the following entries (it should be at least similar to this): $ sudo nano /etc/network/interfaces auto eth0 # IPv4 configuration iface eth0 inet static address 192.168.1.2 network 192.168.1.0 netmask 255.255.255.0 broadcast 192.168.1.255 # IPv6 configuration iface eth0 inet6 static pre-up modprobe ipv6 address 2001:db8:bad:a55::2 netmask 64 Of course, you might have to adjust your interface device (eth0) or you might be interested to have multiple directives for additional devices (eth1, eth2, etc.). The auto instruction takes care that your device is enabled and configured during the booting phase. The use of the pre-up directive depends on your kernel configuration but in most scenarios this might be an optional line. Anyways, it doesn't hurt to have it enabled after all - just to be on the safe side. Next, either restart your network subsystem like so: $ sudo service networking restart Or you might prefer to do it manually with identical parameters, like so: $ sudo ifconfig eth0 inet6 add 2001:db8:bad:a55::2/64 In case that you're logged in remotely into your PC (ie. via ssh), it is highly advised to opt for the second choice and add the device manually. You can check your configuration afterwards with one of the following commands (depends on whether it is installed): $ sudo ifconfig eth0 eth0 Link encap:Ethernet HWaddr 00:21:5a:50:d7:94 inet addr:192.168.160.2 Bcast:192.168.160.255 Mask:255.255.255.0 inet6 addr: fe80::221:5aff:fe50:d794/64 Scope:Link inet6 addr: 2001:db8:bad:a55::2/64 Scope:Global UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 $ sudo ip -6 address show eth0 3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qlen 1000 inet6 2001:db8:bad:a55::2/64 scope global valid_lft forever preferred_lft forever inet6 fe80::221:5aff:fe50:d794/64 scope link valid_lft forever preferred_lft forever In both cases, it confirms that our network device has been assigned a valid IPv6 address. That's it in general for your setup on one system. But of course, you might be interested to enable more services for IPv6, especially if you're already running a couple of them in your IP network. More details are available on the official Ubuntu Wiki. Continue to configure your network to provide IPv6 address information automatically in your local infrastructure. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'configure-ipv6-on-ubuntu.html'; this.page.identifier = '9271ed1c97_idconfigure-ipv6-on-ubuntu'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "connecting-linux-to-watchguard-firebox-ssl.html": {
    "href": "connecting-linux-to-watchguard-firebox-ssl.html",
    "title": "Connecting Linux to WatchGuard Firebox SSL (OpenVPN client) - Get Blogged by JoKi",
    "summary": "Connecting Linux to WatchGuard Firebox SSL (OpenVPN client) Copy Markdown View Markdown GitHub Markdown Source Connecting Linux to WatchGuard Firebox SSL (OpenVPN client) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'connecting-linux-to-watchguard-firebox-ssl'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'connecting-linux-to-watchguard-firebox-ssl'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Connecting Linux to WatchGuard Firebox SSL (OpenVPN client)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-07-23 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Recently, I got a new project assignment that requires to connect permanently to the customer's network through VPN. They are using a so-called SSL VPN. As I am using OpenVPN since more than 5 years within my company's network I was quite curious about their solution and how it would actually be different from OpenVPN. Well, short version: It is a disguised version of OpenVPN. Unfortunately, the company only offers a client for Windows and Mac OS which shouldn't bother any Linux user after all. OpenVPN is part of every recent distribution and can be activated in a couple of minutes - both client as well as server (if necessary). WatchGuard Firebox SSL - About dialog Borrowing some files from a Windows client installation Initially, I didn't know about the product, so therefore I went through the installation on Windows 8. No obstacles (and no restart despite installation of TAP device drivers!) here and the secured VPN channel was up and running in less than 2 minutes or so. Much appreciated from both parties - customer and me. Of course, this whole client package and my long year approved and stable installation ignited my interest to have a closer look at the WatchGuard client. Compared to the original OpenVPN client (okay, I have to admit this is years ago) this commercial product is smarter in terms of file locations during installation. You'll be able to access the configuration and key files below your roaming application data folder. To get there, simply enter '%AppData%\\WatchGuard\\Mobile VPN' in your Windows/File Explorer and confirm with Enter/Return. This will display the following files: Application folder below user profile with configuration and certificate files From there we are going to borrow four files, namely: ca.crt client.crt client.ovpn client.pem and transfer them to the Linux system. You might also be able to isolate those four files from a Mac OS client. Frankly, I'm just too lazy to run the WatchGuard client installation on a Mac mini only to find the folder location, and I'm going to describe why a little bit further down this article. I know that you can do that! Feedback in the comment section is appreciated. Update #1: One of the reader (zer0Sum) provided the path information to retrieve the necessary files on a Mac OS system: /Users/[user]/Library/WatchGuard/Mobile VPN/ Thanks! Update #2: Retrieve the information directly from the WatchGuard Firebox as described in the next paragraph. Get the client configuration file from the WatchGuard Firebox Due to a replacement unit at my customer, I had to update all the certificates here on the client side, too. And as I already changed my main machine I wouldn't like to install the Windows Client software on this computer. Actually, it is not necessary because the certificates can be downloaded from the appliance directly. In order to do this, open your web browser and enter the following URL: https://1.2.3.4/sslvpn.html Note: I changed the IP address of the remote directive above (which should be obvious, right?). This will give you a login dialog like so: Login into the WatchGuard Firebox to get the Mobile VPN with SSL Client information Enter your credentials given by your network administrator and you will be able to download various client information. This is the regular \"Mobile VPN with SSL client\" area: Download the Mobile VPN with SSL Client Profile directly from the WatchGuard appliance We simply ignore the software for Windows and Mac and choose to download the client profile. Save the provided file \"client.ovpn\" to a location on your computer. Now, you can open it with a text editor like Notepad++. Interestingly, the different certificates are stored inside the OpenVPN client configuration file. So, either you leave it as-is or you might consider to cut the certificates from the file and store them as individual files. Both approaches will work. Source: WatchGuard System Manager Help - Use Mobile VPN with SSL with an OpenVPN Client Configuration of OpenVPN (console) Depending on your distribution the following steps might be a little different but in general you should be able to get the important information from it. I'm going to describe the steps in Ubuntu 13.04 (Raring Ringtail). As usual, there are two possibilities to achieve your goal: console and UI. Let's what it is necessary to be done. First of all, you should ensure that you have OpenVPN installed on your system. Open your favourite terminal application and run the following statement: $ sudo apt-get install openvpn network-manager-openvpn network-manager-openvpn-gnome Just to be on the safe side. The four above mentioned files from your Windows machine could be copied anywhere but either you place them below your own user directory or you put them (as root) below the default directory: /etc/openvpn At this stage you would be able to do a test run already. Just in case, run the following command and check the output (it's the similar information you would get from the 'View Logs...' context menu entry in Windows: $ sudo openvpn --config client.ovpn Pay attention to the correct path to your configuration and certificate files. OpenVPN will ask you to enter your Auth Username and Auth Password in order to establish the VPN connection, same as the Windows client. Remote server and user authentication to establish the VPN Please complete the test run and see whether all went well. You can disconnect pressing Ctrl+C. Simplify your life - use an authentication file In my case, I actually set up the OpenVPN client on my gateway/router. This establishes a VPN channel between my network and my client's network and allows me to switch machines easily without having the necessity to install the WatchGuard client on each and every machine. That's also very handy for my various virtualised Windows machines. Anyway, as the client configuration, key and certificate files are located on a headless system somewhere under the roof, it is mandatory to have an automatic connection to the remote site. For that you should first change the file extension '.ovpn' to '.conf' which is the default extension on Linux systems for OpenVPN, and then open the client configuration file in order to extend an existing line. $ sudo mv client.ovpn client.conf $ sudo nano client.conf You should have a similar content to this one here: dev tun client proto tcp-client ca ca.crt cert client.crt key client.pem ;tls-remote \"/O=WatchGuard_Technologies/OU=Fireware/CN=Fireware_SSLVPN_Server\" remote-cert-eku \"TLS Web Server Authentication\" remote 1.2.3.4 443 persist-key persist-tun verb 3 mute 20 keepalive 10 60 cipher AES-256-CBC auth SHA1 floatreneg-sec 3660 nobind mute-replay-warnings auth-user-pass auth.txt ;remember_connection 0 ;auto_reconnect 1 Note: I changed the IP address of the remote directive above (which should be obvious, right?). Anyway, the required change is marked in red and we have to create a new authentication file 'auth.txt'. You can give the directive 'auth-user-pass' any file name you'd like to. Update #3: The OpenVPN directive tls-remote has been deprecated. In the above listed configuration I simply commented the line. The file client.ovpn doesn't have it at all. Due to my existing OpenVPN infrastructure my setup differs completely from the above written content but for sake of simplicity I just keep it 'as-is'. Okay, let's create this file 'auth.txt' $ sudo nano auth.txt and just put two lines of information in it - username on the first, and password on the second line, like so: myvpnusername verysecretpassword Store the file, change permissions, and call openvpn with your configuration file again: $ sudo chmod 0600 auth.txt $ sudo openvpn --config client.conf This should now work without being prompted to enter username and password. In case that you placed your files below the system-wide location /etc/openvpn you can operate your VPNs also via service command like so: $ sudo service openvpn start client $ sudo service openvpn stop client Using Network Manager For newer Linux users or the ones with 'console-phobia' I'm going to describe now how to use Network Manager to setup the OpenVPN client. For this move your mouse to the systray area and click on Network Connections => VPN Connections => Configure VPNs... which opens your Network Connections dialog. Alternatively, use the HUD and enter 'Network Connections'. Network connections overview in Ubuntu Click on 'Add' button. On the next dialog select 'Import a saved VPN configuration...' from the dropdown list and click on 'Create...' Choose connection type to import VPN configuration Now you navigate to your folder where you put the client files from the Windows system and you open the 'client.ovpn' file. Next, on the tab 'VPN' proceed with the following steps (directives from the configuration file are referred): General Check the IP address of Gateway ('remote' - we used 1.2.3.4 in this setup) Authentication Change Type to 'Password with Certificates (TLS)' ('auth-pass-user') Enter User name to access your client keys (Auth Name: myvpnusername) Enter Password (Auth Password: verysecretpassword) and choose your password handling Browse for your User Certificate ('cert' - should be pre-selected with client.crt) Browse for your CA Certificate ('ca' - should be filled as ca.crt) Specify your Private Key ('key' - here: client.pem) Then click on the 'Advanced...' button and check the following values: Use custom gateway port: 443 (second value of 'remote' directive) Check the selected value of Cipher ('cipher') Check HMAC Authentication ('auth') Enter the Subject Match: /O=WatchGuard_Technologies/OU=Fireware/CN=Fireware_SSLVPN_Server ('tls-remote') Finally, you have to confirm and close all dialogs. You should be able to establish your OpenVPN-WatchGuard connection via Network Manager. For that, click on the 'VPN Connections => client' entry on your Network Manager in the systray. It is advised that you keep an eye on the syslog to see whether there are any problematic issues that would require some additional attention. Advanced topic: routing As stated above, I'm running the 'WatchGuard client for Linux' on my head-less server, and since then I'm actually establishing a secure communication channel between two networks. In order to enable your network clients to get access to machines on the remote side there are two possibilities to enable that: Proper routing on both sides of the connection which enables both-direction access, or Network masquerading on the 'client side' of the connection Following, I'm going to describe the second option a little bit more in detail. The Linux system that I'm using is already configured as a gateway to the internet. I won't explain the necessary steps to do that, and will only focus on the additional tweaks I had to do. You can find tons of very good instructions and tutorials on 'How to setup a Linux gateway/router' - just use Google. OK, back to the actual modifications. First, we need to have some information about the network topology and IP address range used on the 'other' side. We can get this very easily from /var/log/syslog after we established the OpenVPN channel, like so: $ sudo tail -n20 /var/log/syslog Or if your system is quite busy with logging, like so: $ sudo less /var/log/syslog | grep ovpn The output should contain PUSH received message similar to the following one: Jul 23 23:13:28 ios1 ovpn-client[789]: PUSH: Received control message: 'PUSH_REPLY,topology subnet,route 192.168.1.0 255.255.255.0,dhcp-option DOMAIN,route-gateway 192.168.6.1,topology subnet,ping 10,ping-restart 60,ifconfig 192.168.6.2 255.255.255.0' The interesting part for us is the route command which I highlighted already in the sample PUSH_REPLY. Depending on your remote server there might be multiple networks defined (172.16.x.x and/or 10.x.x.x). Important: The IP address range on both sides of the connection has to be different, otherwise you will have to shuffle IPs or increase your the netmask. After the VPN connection is established, we have to extend the rules for iptables in order to route and masquerade IP packets properly. I created a shell script to take care of those steps: #!/bin/sh -e IPTABLES=/sbin/iptables DEV_LAN=eth0 DEV_VPNS=tun+ VPN=192.168.1.0/24 $IPTABLES -A FORWARD -i $DEV_LAN -o $DEV_VPNS -d $VPN -j ACCEPT $IPTABLES -A FORWARD -i $DEV_VPNS -o $DEV_LAN -s $VPN -j ACCEPT $IPTABLES -t nat -A POSTROUTING -o $DEV_VPNS -d $VPN -j MASQUERADE I'm using the wildcard interface 'tun+' because I have multiple client configurations for OpenVPN on my server. In your case, it might be sufficient to specify device 'tun0' only. Simplifying your life - automatic connect on boot Now, that the client connection works flawless, configuration of routing and iptables is okay, we might consider to add another 'laziness' factor into our setup. Due to kernel updates or other circumstances it might be necessary to reboot your system. Wouldn't it be nice that the VPN connections are established during the boot procedure? Yes, of course it would be. To achieve this, we have to configure OpenVPN to automatically start our VPNs via init script. Let's have a look at the responsible 'default' file and adjust the settings accordingly. $ sudo nano /etc/default/openvpn Which should have a similar content to this: # This is the configuration file for /etc/init.d/openvpn # # Start only these VPNs automatically via init script. # Allowed values are \"all\", \"none\" or space separated list of # names of the VPNs. If empty, \"all\" is assumed. # The VPN name refers to the VPN configutation file name # i.e. \"home\" would be /etc/openvpn/home.conf AUTOSTART=\"all\" #AUTOSTART=\"none\" #AUTOSTART=\"home office\" # # ... more information which remains unmodified ... With the OpenVPN client configuration as described above you would either set AUTOSTART to \"all\" or to \"client\" to enable automatic start of your VPN(s) during boot. You should also take care that your iptables commands are executed after the link has been established, too. You can easily test this configuration without reboot, like so: $ sudo service openvpn restart Enjoy stable VPN connections between your Linux system(s) and a WatchGuard Firebox SSL remote server. Cheers, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'connecting-linux-to-watchguard-firebox-ssl.html'; this.page.identifier = '9271ed1c97_idconnecting-linux-to-watchguard-firebox-ssl'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "contact.html": {
    "href": "contact.html",
    "title": "Contact - Get Blogged by JoKi",
    "summary": "Contact Copy Markdown View Markdown GitHub Markdown Source Contact Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'contact'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'contact'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Contact') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); Whether you have a question about software architecture, .NET development, Google Cloud & Gemini AI integrations, want to invite me for a speaking engagement, or simply want to connect with the local Mauritius tech community, I am always happy to hear from fellow software crafters, developers, and tech leaders. Direct Communication Email: jochen@kirstaetter.name For direct inquiries, technical collaboration, speaking proposals, and community partnerships. Location: Flic en Flac, Mauritius (Indian Ocean / UTC+4) Social & Professional Networks You can reach me or follow my ongoing technical work across various developer and social platforms: Twitter / X: @JKirstaetter - Daily technical commentary, quick insights, and updates. BlueSky: @jochen.kirstaetter.name - Decentralised social discussions and developer threads. Mastodon: @JKirstaetter - Open-source and Fediverse conversations. LinkedIn: Jochen Kirstätter - Professional career history, enterprise connections, and awards. GitHub: @jochenkirstaetter - Open source projects, code samples, and GhostFx source code. Facebook: jochen.kirstaetter - Personal and community updates. Community & Public Events If you are visiting or living in Mauritius, you can meet me in person at local tech gatherings: Mauritius Software Craftsmanship Community (MSCC): Monthly meetups, coding sessions, and tech discussions. Developers Conference Mauritius: Annual multi-day tech conference in Mauritius organised by the MSCC. Google Developer Group (GDG) Mauritius: Google Cloud, Android, AI, and Web community events. RSS & Feeds Stay up to date with new articles, deep-dives, and technical tutorials by subscribing to the blog RSS feed or following along on Feedly."
  },
  "coverage-der-12-vfp-entwicklerkonferenz.html": {
    "href": "coverage-der-12-vfp-entwicklerkonferenz.html",
    "title": "Coverage der 12. VFP Entwicklerkonferenz - Get Blogged by JoKi",
    "summary": "Coverage der 12. VFP Entwicklerkonferenz Copy Markdown View Markdown GitHub Markdown Source Coverage der 12. VFP Entwicklerkonferenz Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'coverage-der-12-vfp-entwicklerkonferenz'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'coverage-der-12-vfp-entwicklerkonferenz'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Coverage der 12. VFP Entwicklerkonferenz') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-14 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wahnsinn! Ich hab' gerade kurz mal in der Mittagspause die Gelegenheit genutzt und mir die Online Coverage der German Visual FoxPro Developer Conference 2005 durchgelesen. Klasse! Die Arbeit von Armin und Jan ist absolut klasse! Danke für diese coole Berichterstattung. Danach hab' ich einen Abstecher ins 'Gruselkabinett' - aka Picture Gallery -gemacht. Cool, was da wieder an Schnappschüssen entstanden ist. Selbst ich bin auf ein paar Bilderchen vertreten... In manchen Fällen wäre es besser gewesen, wenn nicht. Hier eine Auswahl meiner Lieblingsschnappschüsse: \"Briefing \" für die Redner Begrüßung zur Konferenz Live-Berichterstattung hier im Blog Speaker @ Work!! Fitness-Training nach dem Essen VFP Business as usual ... und viele weitere Bilder Schaut euch die Galerie an. Es lohnt sich! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'coverage-der-12-vfp-entwicklerkonferenz.html'; this.page.identifier = '9271ed1c97_idcoverage-der-12-vfp-entwicklerkonferenz'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "create-nuget-packages-vs-code.html": {
    "href": "create-nuget-packages-vs-code.html",
    "title": "Create NuGet packages with Visual Studio / Visual Studio Code - Get Blogged by JoKi",
    "summary": "Create NuGet packages with Visual Studio / Visual Studio Code Copy Markdown View Markdown GitHub Markdown Source Create NuGet packages with Visual Studio / Visual Studio Code Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'create-nuget-packages-vs-code'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'create-nuget-packages-vs-code'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Create NuGet packages with Visual Studio / Visual Studio Code') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-03-01 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Using NuGet packages gives developers a great amount of flexibility and fast pace to implement their own ideas. Whether you are using packages from other authors or prefer to use your own ones. The following article summarises my findings in how to get started building NuGet packages with Visual Studio, Visual Studio Code, or even the command line only. During one of the last assignments and more recently working on some Gemini AI related coding I came across the need for NuGet packages. What is NuGet? An essential tool for any modern development platform is a mechanism through which developers can create, share, and consume useful code. Often such code is bundled into \"packages\" that contain compiled code (as DLLs) along with other content needed in the projects that consume these packages. For more background information I highly recommend reading Taking on DLL hell and follow-up articles by David Ebbo. For .NET (including .NET Core), the Microsoft-supported mechanism for sharing code is NuGet, which defines how packages for .NET are created, hosted, and consumed, and provides the tools for each of those roles. Following is the flow of NuGet package. Learn more about NuGet in the documentation. Although there are multiple tools to handle NuGet packages this article is mainly focusing on the dotnet CLI tool. Creating a new project Whether you start with a clean slate or converting/extending an existing project is nearly the same experience. For clarity I'm going to create a new dotnet project and guide you through the steps to add more functionality to the NuGet package. dotnet new classlib Despite having no functionality it is already possible to create a NuGet package using such an empty project. dotnet pack However, you would be stuck with the default values for the moment. Which we won't accept. \"Don't accept the defaults!\" – Abel Wang The command output has the full path information to the generated .nupkg file. Which is a ZIP archive and can be inspected either using an archive manager or the built-in features of the file manager. Targeting a .NET version While creating a new project you either use the target versions of .NET or the project template defines it for you. When you open a .NET project file, usually a csproj file for C# you might notice the XML element TargetFramework. <Project Sdk=\"Microsoft.NET.Sdk\"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> </Project> The allowed value of TargetFramework are .NET specific version moniker and instructs the compiler which version of .NET to target for your assembly. Multi-targeting .NET versions You have seen it already with numerous other NuGet packages that are targetting multiple versions of .NET. Ranging from elderly versions of .NET Framework, and therefore Windows only, to the latest preview versions of .NET (Core). How is this done? Using .NET ability to allow targetting multiple versions using one single project. Open your .NET project .csproj file and change the TargetFramework element to its plural version TargetFrameworks and add more target framework monikers (TFM) separated by semicolons, like so. <Project Sdk=\"Microsoft.NET.Sdk\"> <PropertyGroup> <TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks> </PropertyGroup> </Project> Learn more about multi-targetting for NuGet packages and how to support multiple .NET versions in your project file. Package attributes in your project file There are a number of attributes for NuGet packages that can be added to the project file. Newer versions of Visual Studio (2022 and onwards) provide better options and most of those values can be configured in the IDE under the properties of the project. As we are using MSBuild to pack the assembly and create the NuGet package, we use MSBuild properties to configure and describe the attributes of the package. Let's have a look at some essential MSBuild properties which might cover the majority of needs. <Project Sdk=\"Microsoft.NET.Sdk\"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <PackageId>MSCC.GenerativeAI</PackageId> <Version>0.5.42</Version> <Description> A simple package to integrate generative AI into an application. </Description> <PackageProjectUrl>https://mscraftsman.github.io/</PackageProjectUrl> <RepositoryUrl>https://github.com/mscraftsman/generative-ai</RepositoryUrl> <Authors>Jochen Kirstätter</Authors> <Copyright>Copyright (c) Jochen Kirstätter</Copyright> <PackageReadmeFile>README.md</PackageReadmeFile> <PackageIcon>mscc-nuget.png</PackageIcon> <PackageTags>genAI;Generative AI;Gemini;Vertex AI</PackageTags> <PackageLicenseFile>LICENSE</PackageLicenseFile> <PackageRequireLicenseAcceptance>False</PackageRequireLicenseAcceptance> <PackageReleaseNotes>- Update README.md</PackageReleaseNotes> <GeneratePackageOnBuild>True</GeneratePackageOnBuild> <PropertyGroup> </Project> The package identifier and the version number uniquely identify the exact code that's contained in the package. This is important to know. Once a package has been added to a NuGet source it cannot be modified anymore or replaced. Any changes to the source code or package properties require a new build and pack process. Hence, the package identifier and the version number uniquely identify the exact code that's contained in the package. A list of all MSBuild properties is found in the reference section of the documentation: NuGet pack and restore as MSBuild targets. After package creation you can still check and inspect the generated .nupkg file to see whether all intended content is present. Note: A NuGet package with the .nupkg extension is simply a ZIP file. To easily examine any package's contents, change the extension to .zip and expand its contents as usual. Just be sure to change the extension back to .nupkg before attempting to upload it to a host. This also allows you to quickly review the generated .nuspec file. You can check all assets needed per targetted .NET version. Using a NuSpec file In case that the available MSBuild properties are not sufficient enough to define all package atrributes, it is still possible to add a .nuspec file to the project with more details. Learn more about packing using a .nuspec file. Deploy a NuGet package After completing the dotnet pack command you can distribute the .nupkg file to a shared store or publish it to a NuGet source. Use a network drive Because pack and restore are MSBuild targets, you can access them to enhance your workflow. For example, let's say you want to copy your package to a network share after packing it. You can do that by adding the following in your project file: <Target Name=\"CopyPackage\" AfterTargets=\"Pack\"> <Copy SourceFiles=\"$(OutputPath)..\\$(PackageId).$(PackageVersion).nupkg\" DestinationFolder=\"\\\\myshare\\packageshare\\\" /> </Target> Azure Artifacts Quite often there is a correlation between projects written in .NET and using Azure DevOps for continuous integration (CI) and continuous deployment (CD). One and more pipelines in Azure DevOps are used to create, pack, and deploy projects. The resulting assemblies, or in this case, the NuGet package can then be pushed to Azure Artifacts and accessed by other developer that have access to that store. Probably, it is an option to distribute NuGet packages among smaller teams of developers. More on this approach below. NuGet Gallery If you are comfortable publishing your package to the rest of the world then the NuGet Gallery might be your first choice to spread your codes. Setting up an account is straight forward and a matter of seconds using a Microsoft account. Then upload the .nupkg file to the nuget.org web portal and you are officially published as a NuGet package maintainer. Alternatively, you can push the package using a command line. To push packages to nuget.org with a command line, you can use either dotnet.exe or nuget.exe v4.1.0 or above, which implement the required NuGet protocols. All the necessary steps are largely described in Push NuGet packages in the official documentation. Third party systems There are other free and proprietary solutions to handle packages and source feeds within your organisation. In the past I used to work with the likes of ProGet and MyGet. BTW, all that could be integrated into a CI/CD pipeline with minimal effort. Summary Using NuGet packages whether in context of private, in-house development or public sets boundaries between different responsibilities, capabilities, and functionalities of an application. Furthermore, it allows that development can be done as independent units and provides more stability across multiple projects and therefore development teams. New versions of packages can be developed without having concerns to break existing projects by others that are incorporating that functionality. And with access to previous versions of a NuGet package software developers have peace of mind regarding their decision when to upgrade and more importantly that a rollback is possible anytime in case there might be breaking changes. Access to pre-release versions of a NuGet package can easily be handled alongside the releases of stable versions. Image credit: Gemini using prompt \"an image showing the creation of packages\" if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'create-nuget-packages-vs-code.html'; this.page.identifier = '9271ed1c97_idcreate-nuget-packages-vs-code'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "cs-career-advice-aber.html": {
    "href": "cs-career-advice-aber.html",
    "title": "Career Guidance (Day) at Aberystwyth University - Get Blogged by JoKi",
    "summary": "Career Guidance (Day) at Aberystwyth University Copy Markdown View Markdown GitHub Markdown Source Career Guidance (Day) at Aberystwyth University Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'cs-career-advice-aber'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'cs-career-advice-aber'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Career Guidance (Day) at Aberystwyth University') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-09-26 - Filed under CommunityGeneral (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); More of spontaneous coincidence than a planned activity I was contacted by Dr. Tomi A. Pasanen, Lecturer in Computer Science at the Aberystwyth University, Mauritius Branch Campus, whether I'd be interested to give a talk on Computer Science (CS) career advice. Having met Tomi already earlier this year during the It's Just Angular meeting of the MSCC, and knowing his genuine interest in the IT activities of local user groups in Mauritius, I agreed to come over on a very short notice. We exchanged briefly about the general direction of the talk; which could be summarised like this: \"The point for students could be somehow to understand the value they get through studies. Computer Science is always never heard topic at schools and options after study are not clear. Many think that it is only hard programming but quite few students will actually do that for whole life.\" and it was settled literally. Today I went to the campus near Quartier Militaire, actually it's not far from my office in Quatre Bornes. Tomi welcomed me nicely and while CS students (together with others) were still occupied with other lecturers, we seized the opportunity to align our ideas and expectations of the career guidance talk over a cup of tea. Typical British style, even tough Tomi is Finnish. \uD83D\uDE09 During the break just before my talk he introduced me to Dr Susan Busby, Academic Campus Director and Lecturer in Business and Management, who was pleased about my appearance and assistance for the Aber Career Guidance day. She gave me a little insight into the demographics at the university, and emphasised the importance of an early exchange between students and real-world requirements in Computer Science. A little bit after schedule we met in the Computer Lab 1 of the Aber university, and giving it some minutes for students to arrive. Following are today's bullet points. Curiousity Always stay hungry and explore new things. Watch, observe and learn from others. Eventually, we found common ground given the experience that the present CS students took apart tech-toys during their youth in order to figure out how this stuff might actually work. Sometimes with irreversible result... Creativity Understanding requirements, solving problems and writing appropriate solutions in software demands creativity and eventually sparks of \"genius\". In fact, I'm convinced that working in Computer Science and related fields is mainly about creating source code, not typing. Literally, one could say that writing source code is a certain type of art. Although, I wouldn't consider myself an artist after all. Passion Studying CS at the university is (just) a stage to pass. It's an entrance into a professional career as a software developer, a cyber security researcher, or a data scientist. Get an understanding that your studies are the fundament of your career. Develop your interest and passion for IT and computer-related fields. Have this \"feel good factor\" while sitting or standing in front of your machine. Perseverance No worries, there will be drawbacks and road bumps all the way through. A certain level of sturdiness and steady persistence will be a good trait in your career. Together with constant (and maybe daily) learning about new technologies, new techniques and practices it will help to develop your knowledge in Computer Science. When it gets tough, keep on going... Craftsmanship \"Practice makes Perfect\" - what matters for blue-collar jobs directly reflects on Computer Science, too. You learn to write (better) code by writing code. Actually, the majority of your time in software development might be reading and reviewing your own and other's source code. By literally breathing source code - practice, practice and practice more - you are going to constantly improve your skills and understanding of Computer Science. Understanding software development as a craft which requires regular practice is very close either to the habits of an actual craftsman (read: carpenter, welder, goldsmith, etc) or the katas of a martial arts fighter. Community Get in touch with more experienced software developers and/or IT people in general as earliest as possible. Try to find a mentor in Computer Science who would be available for your questions, and might be able to guide you during your studies. Look out for local user groups and communities, like the MSCC or any other technology-focused group. Go out and meet others. The advantage of networking, exchange of experience, and discussion with others will be a boost for you. Eventually, every CS student should be exposed to The Code Manifesto on the first day of studies. Choose wisely and Master your tools Last but not least, I shared my experience on choice of tools. Both, in regards to hardware like a proper mechnical keyboard, a decent high-performance mouse or eventually better a track ball, and an ergonomic chair, and towards the right software. Yes, you can write source code using a plain text editor but would it productive and a joy to do so? Personally, I can't use a mouse over a longer period without suffering from muscle tensions in neck and shoulder. Hence I prefer to use a track ball device since more than 15 years. Unfortunately, we ran a bit out of time, as I would have liked to talk about the following, too. Ethics As a student in Computer Science you are thriving to have a career most likely as a software developer. Meaning your future salary will be based on the sales or licensing of the applications and products you'll work on. At this stage you won't be earning money, maybe it's the opposite actually as you have to pay for your education. Nevertheless, it's not an excuse to or reason to use pirated software on your computer. There are so many alternatives available. Software companies usually have academic, personal use or student-only licenses at a smaller price or even for free. Check out their website, and if nothing is written get in touch with their sales department. Look into Free and Open Source Software (FOSS). There is a whole universe waiting for you... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'cs-career-advice-aber.html'; this.page.identifier = '9271ed1c97_idcs-career-advice-aber'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ctp-fr-sedna-und-visual-foxpro-90-sp2.html": {
    "href": "ctp-fr-sedna-und-visual-foxpro-90-sp2.html",
    "title": "CTP für Sedna und Visual FoxPro 9.0 SP2 - Get Blogged by JoKi",
    "summary": "CTP für Sedna und Visual FoxPro 9.0 SP2 Copy Markdown View Markdown GitHub Markdown Source CTP für Sedna und Visual FoxPro 9.0 SP2 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ctp-fr-sedna-und-visual-foxpro-90-sp2'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ctp-fr-sedna-und-visual-foxpro-90-sp2'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'CTP für Sedna und Visual FoxPro 9.0 SP2') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-14 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Das solche Neuigkeiten immer kurz vor dem Wochenende kommen müssen. Tss, da ist man gedanklich schon auf Entspannung und dann wird man von Microsoft mit neuen Gimmicks versorgt. Ausgelöst durch einen RSS Feed auf andere Blogs habe ich erfahren, dass sowohl ein neuer Community Techology Preview (CTP) für Sedna wie auch für das kommende Service Pack 2 für Visual FoxPro 9.0 veröffentlicht wurden. Sedna und NET4COM Fein, gerade in Bezug auf NET4COM interessiert mich natürlich die Fassung für meine Session auf der diesjährigen Entwicklerkonferenz. Inzwischen ist das .NET Framework 2.0 als Grundlage für NET4COM rangezogen worden. Die Beispiele in Visual FoxPro wurden ergänzt und es gibt eine entsprechende FoxPro Foundation Class (FFC) für die einfache Nutzung des COM-Servers. Aktuell habe ich mir die Details noch nicht angeschaut, aber das wird noch passieren. Hm, zusätzlich muss ich nun auch meine Sessionnotizen überarbeiten und aktualisieren... Danke... \uD83D\uDE01 Service Pack 2 Herrje, vor dem Vergnügen zuerst die Arbeit - anders kann man die Installationsorgie bedingt durch den Microsoft Windows Installer nicht bezeichnen. Aber hey, ich bin doch selbst schuld. Wie kann man auf die Idee kommen und keine Standardpfade zu verwenden. Ist ja schon eine Frechheit, dass man lokale VFP Installation von einem Netzwerk ausgehend installiert wurde. Nunja, da ich nicht in der Firma bin, klappt das Update auf SP2 schon mal prinzipiell nicht. Okay, kein Thema nehmen wir die DVD aus dem MSDN-Package und korrigieren den Installationspfad beim Update... No chance! Gut, passen wir die Pfadinfos in der Registry an und probieren es erneut. Schon besser, aber bei der Aufforderung zur Einlage der 'Disk 1' hört der Spass auf. Na gut, dann eben komplette Deinstallation von VFP 9.0, Reboot, dann frische blanke Installation von VFP 9.0 - wow, erneuter Reboot durch WCUs - und nun endlich zeigt sich auch das Update auf SP2 kooperativ. Es kann eventuell daran gelegen haben, dass das Update SP2 ein frisches VFP 9.0 - also ohne SP1 - erfordert... who knows. Ich werde meine Erkenntnisse zunächst noch an das Supportcenter, dass es bei der Final einfachen wird. Die neue Versionsnummer lautet übrigens: Visual FoxPro 09.00.0000.4611 for Windows Und interessanterweise bzw. auch stillschweigend wurde der OLE DB Provider für Visual FoxPro 9.0 ebenfalls aktualisiert, was aber bisher nirgends dokumentiert ist. Und der OLE DB Provider eigentlich separat vom Hauptprodukt als Download angeboten wird. Verschleierungstaktik von Microsoft? Aktualisierter RuntimeInstaller Als langjährigen Service bietet ProLib die entsprechenden Runtimeinstaller für die Auslieferung von VFP-Anwendungen beim Kunden kostenfrei an. Damit braucht man sich selbst keine Gedanken mehr über Dateien und Registrierungen und so machen. Laufen lassen, Einstellungen setzen und wohlfühlen. Unsere Installer können übrigens auch in eigene Setups integriert werden. Viel Spass mit dem CTP des Service Pack 2 für VFP 9.0, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ctp-fr-sedna-und-visual-foxpro-90-sp2.html'; this.page.identifier = '9271ed1c97_idctp-fr-sedna-und-visual-foxpro-90-sp2'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "curse-of-magical-tutorial.html": {
    "href": "curse-of-magical-tutorial.html",
    "title": "The Curse of the Magical Tutorial - Get Blogged by JoKi",
    "summary": "The Curse of the Magical Tutorial Copy Markdown View Markdown GitHub Markdown Source The Curse of the Magical Tutorial Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'curse-of-magical-tutorial'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'curse-of-magical-tutorial'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'The Curse of the Magical Tutorial') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-10-05 - Filed under CommunityPersonal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Have you ever read a tutorial about something that you are new to, and really need to get into the subject quickly, just to discover that there is an essential piece of information missing? Yup, happens to me all the time... Okay, to be fair not really all the time but unfortunately still too often. Especially when I'm scavenging the interweb for some crucial information or instructions about how to get started with technology X, how to use product Y efficiently, or to solve problem Z in a sustainable manner for good. Writing is not for the faint of heart Writing is a tough trade, and even more in regards to writing a tutorial. Not to mention a complete tutorial. I understand that a lot of time and energy goes into an article by the originating author. But please try to envision the point of view of your reader, too. That's one small step for [a] man, one giant leap for mankind. – Neil Armstrong The reader of a tutorial is not as familiar with the subject as you, the writer, might be, and this is something that should be taken into consideration before hitting that weird Publish button. As a reader, having to deal with an incomplete tutorial - or magical tutorial as I like to call them - is utterly frustrating for several reasons. Unfamiliarity with the subject First of all, your reader is probably new to the domain hence reading your article about it. With this comes a good chunk of insecurity and lack of (background) knowledge to make an informed decision about bits and pieces that are described in the tutorial. At this stage a reader fully trusts in the author's expertise on the subject. At least for me, it is quite annoying to follow each particular step in a tutorial meticulously only to realise at a certain stage that the progress does a magical jump from A to B - without providing any further details about what just happened... Add those small details Second, review your content before you publish it. And this should include peer reviewing. Yes, reach out to your life partner, colleague at work, or perhaps members from your community and ask them kindly for their feedback. Perhaps you might even have an editor that not only proof-reads your content but also checks your instructions or code samples. Having an editorial team is something absolutely normal in regards to publishing a book, so why shouldn't this apply to posting a blog article, too? While writing a tutorial I'm trying to change my own point of view. Meaning, I virtually take of the author's cap and put on the newbie's hat. Then I follow my own instructions each single step. If there are snippets of source code I create a new project and copy and paste the code to see what's missing. In case of commands on the CLI I open the terminal and I run the statements to see whether it works as expected and to compare the output to what I might have documented. Additionally, I learned something from John O'Nolan, the creator of the Ghost blogging platform: Write drunk; edit sober – Ernest Hemingway And as John stated it - Regardless of whether or not he ever actually said it (we're sure you'll let us know in the comments), the notion is a good one. - I have to agree. Give your content some time to settle... in your mind. I usually let my articles rest at least over night or sometimes give them several days before I make them public. This changes my perception of the tutorial as I'm not too deep into the writing process anymore and my focus is less narrowed down to accidentally overlook those important small details. Provide additional material Third and this clearly depends on the nature of your tutorial, consider to provide your source material to your readers. For example, in case of an article on software development either offer an option to download the whole stuff or link to a public source code repository. Personally, I would prefer latter as this opens up the possibility that your readers can document an issue or even send you a pull request with changes and improvements to your code base. One single article or a series of articles? Depending on the amount of information or the complexity of the domain it might be a positive notion to split a tutorial into several chunks, then give them a specific order and interlink them. It's like chapters in a book; break it down for your readers and give them a better experience to follow your instructions. Perhaps you might be interested to check out my series of articles on using IPv6 protocol in Linux as an example, which is broken down into four pieces: Configure IPv6 on your Linux system DHCPv6: Provide IPv6 information in your local network Enabling DNS for IPv6 infrastructure Accessing your web server via IPv6 Each single article can stand on its own but it is the combination of all in the series to give you the complete picture and result you might be looking for. It's not all about pixie dust Writing an article on a certain topic is quite an effort; sometimes hard work, and not at all some kind of voodoo or magical pixie dust. A tutorial is also a way to document your own experience about dealing with a specific problem and most likely how to solve it. Remind yourself about how you started and empathise the pain your reader might encounter while reading your content. As soon as you know the solution the whole problem doesn't appear to be that complicated anymore. Keep this in mind while writing and reviewing your work. If you cannot explain it in simple terms it is either not fully understood or still to complex and needs more attention to those small details. Don't skip anything; instead give it some more time... What are your thoughts on magical tutorials? Please leave a comment to elaborate on this topic. Thanks. Cover image courtesy of Jason Leung if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'curse-of-magical-tutorial.html'; this.page.identifier = '9271ed1c97_idcurse-of-magical-tutorial'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "custom-page-sizes-in-paging-dropdown-in-telerik-radgrid.html": {
    "href": "custom-page-sizes-in-paging-dropdown-in-telerik-radgrid.html",
    "title": "Custom page sizes in paging dropdown in Telerik RadGrid - Get Blogged by JoKi",
    "summary": "Custom page sizes in paging dropdown in Telerik RadGrid Copy Markdown View Markdown GitHub Markdown Source Custom page sizes in paging dropdown in Telerik RadGrid Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'custom-page-sizes-in-paging-dropdown-in-telerik-radgrid'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'custom-page-sizes-in-paging-dropdown-in-telerik-radgrid'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Custom page sizes in paging dropdown in Telerik RadGrid') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-11-08 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Working with Telerik RadControls for ASP.NET AJAX is actually quite easy and the initial effort to get started with the control suite is very low. Meaning that you can easily get good result with little time. But there are usually cases where you have to go a little further and dig a little bit deeper than the standard scenarios. In this article I am going to describe how you can customize the default values (10, 20 and 50) of the drop-down list in the paging element of RadGrid. Get control over the displayed page sizes while using numeric paging... The default page sizes are good but not always good enough The paging feature in RadGrid offers you 3, well actually 4, possible page sizes in the drop-down element out-of-the box, which are 10, 20 or 50 items. You can get a fourth option by specifying a value different than the three standards for the PageSize attribute, ie. 35 or 100. The drawback in that case is that it is the initial page size. Certainly, the available choices could be more flexible or even a little bit more intelligent. For example, by taking the total count of records into consideration. There are some interesting scenarios that would justify a customized page size element: A low number of records, like 14 or similar shouldn't provide a page size of 50, A high total count of records (ie: 300+) should offer more choices, ie: 100, 200, 500, or display of all records regardless of number of records I am sure that you might have your own requirements, and I hope that the following source code snippets might be helpful. Wiring the ItemCreated event In order to adjust and manipulate the existing RadComboBox in the paging element we have to handle the OnItemCreated event of RadGrid. Simply specify your code behind method in the attribute of the RadGrid tag, like so: <telerik:RadGrid ID=\"RadGridLive\" runat=\"server\" AllowPaging=\"true\" PageSize=\"20\" AllowSorting=\"true\" AutoGenerateColumns=\"false\" OnNeedDataSource=\"RadGridLive_NeedDataSource\" OnItemDataBound=\"RadGrid_ItemDataBound\" OnItemCreated=\"RadGrid_ItemCreated\"> <ClientSettings EnableRowHoverStyle=\"true\"> <ClientEvents OnRowCreated=\"RowCreated\" OnRowSelected=\"RowSelected\" /> <Resizing AllowColumnResize=\"True\" AllowRowResize=\"false\" ResizeGridOnColumnResize=\"false\" ClipCellContentOnResize=\"true\" EnableRealTimeResize=\"false\" AllowResizeToFit=\"true\" /> <Scrolling AllowScroll=\"true\" ScrollHeight=\"360px\" UseStaticHeaders=\"true\" SaveScrollPosition=\"true\" /> <Selecting AllowRowSelect=\"true\" /> </ClientSettings> <MasterTableView DataKeyNames=\"AdvertID\"> <PagerStyle AlwaysVisible=\"true\" Mode=\"NextPrevAndNumeric\" /> <Columns> <telerik:GridBoundColumn HeaderText=\"Listing ID\" DataField=\"AdvertID\" DataType=\"System.Int32\" SortExpression=\"AdvertID\" UniqueName=\"AdvertID\"> <HeaderStyle Width=\"66px\" /> </telerik:GridBoundColumn> <!--// ... and some more columns ... --> </Columns> </MasterTableView> </telerik:RadGrid> To provide a consistent experience for your visitors it might be helpful to display the page size selection always. This is done by setting the AlwaysVisible attribute of the PagerStyle element to true, like highlighted above. Customize the values of page size Your delegate method for the ItemCreated event should look like this: protected void RadGrid_ItemCreated(object sender, GridItemEventArgs e) { if (e.Item is GridPagerItem) { var dropDown = (RadComboBox)e.Item.FindControl(\"PageSizeComboBox\"); var totalCount = ((GridPagerItem)e.Item).Paging.DataSourceCount; var sizes = new Dictionary<string, string>() { {\"10\", \"10\"}, {\"20\", \"20\"}, {\"50\", \"50\"} }; if (totalCount > 100) { sizes.Add(\"100\", \"100\"); } if (totalCount > 200) { sizes.Add(\"200\", \"200\"); } sizes.Add(\"All\", totalCount.ToString()); dropDown.Items.Clear(); foreach (var size in sizes) { var cboItem = new RadComboBoxItem() { Text = size.Key, Value = size.Value }; cboItem.Attributes.Add(\"ownerTableViewId\", e.Item.OwnerTableView.ClientID); dropDown.Items.Add(cboItem); } dropDown.FindItemByValue(e.Item.OwnerTableView.PageSize.ToString()).Selected = true; } } It is important that we explicitly check the event arguments for GridPagerItem as it is the control that contains the PageSizeComboBox control that we want to manipulate. To keep the actual modification and exposure of possible page size values flexible I am filling a Dictionary with the requested 'key/value'-pairs based on the number of total records displayed in the grid. As a final step, ensure that the previously selected value is the active one using the FindItemByValue() method. Of course, there might be different requirements but I hope that the snippet above provide a first insight into customized page size value in Telerik's Grid. The Grid demos describe a more advanced approach to customize the Pager. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'custom-page-sizes-in-paging-dropdown-in-telerik-radgrid.html'; this.page.identifier = '9271ed1c97_idcustom-page-sizes-in-paging-dropdown-in-telerik-radgrid'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "customise-jitsi-meet.html": {
    "href": "customise-jitsi-meet.html",
    "title": "Customise Your Instance of Jitsi Meet - Get Blogged by JoKi",
    "summary": "Customise Your Instance of Jitsi Meet Copy Markdown View Markdown GitHub Markdown Source Customise Your Instance of Jitsi Meet Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'customise-jitsi-meet'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'customise-jitsi-meet'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Customise Your Instance of Jitsi Meet') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-04-20 - Filed under DevelopmentToolingLinux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); In the previous two articles of this series, we set up our self-hosted video conferencing infrastructure. First, we walked through how to Install Jitsi Meet on Compute Engine (GCP), providing a scalable, isolated WebRTC server. Next, we secured our installation against unauthorised room squatting by learning how to Enable Authentication in Jitsi Meet. Now that your instance is operational and secure, it is time to make it truly your own. Out of the box, Jitsi Meet displays default branding, external links to the Jitsi community, and generic meeting room titles. Customising the interface to display your own community logo, custom colour scheme, tailored welcome text, and default audio/video behaviour transforms a generic utility into a professional, cohesive communication platform. However, anyone who has modified a Debian or Ubuntu package installation knows the frustration of running apt-get upgrade only to find that their custom logos, stylesheets, and configurations have been completely overwritten. In this third article, we will explore: Core feature configuration in hostname-config.js. Visual appearance customisation covering logos, watermarks, titles, and welcome content. Practical strategies to make customisations upgrade-persistent across package updates with hardened Nginx rules and caching. Google Calendar integration for agenda synchronisation on the landing page and user settings. Desktop and mobile client integration, including in-browser WebRTC vs native mobile app server configuration. Note The adjustments described below require administrative permissions. You can execute them by launching an interactive root terminal (sudo -i) or by prefixing individual commands with sudo. Core feature configuration The primary configuration file controlling client-side behaviour is located at: sudo nano /etc/jitsi/meet/$(hostname -f)-config.js This JavaScript file is loaded by every participant's browser before entering a room. It governs meeting defaults, audio/video policies, and third-party integrations (consult the upstream Jitsi Meet Web Integrations Guide for the full parameter schema). Essential participant settings When hosting large meetups or webinars, having every attendee enter with microphones and cameras blazing creates immediate audio feedback and bandwidth congestion. You can set sane defaults: // Start every participant muted to avoid immediate audio feedback startWithAudioMuted: true, // Start participants with video muted if bandwidth is constrained startWithVideoMuted: false, // Require participants to verify mic and camera in a prejoin lobby prejoinPageEnabled: true, // Default UI language (e.g. English) defaultLanguage: 'en', // Enable or disable peer-to-peer mode for 1-on-1 calls p2p: { enabled: true, preferH264: true } Branding and appearance customisation The visual presentation of Jitsi Meet is distributed across static HTML templates, configuration files, and image assets located in /usr/share/jitsi-meet/. Custom watermark & logos The most recognisable element in a Jitsi call is the watermark shown in the top-left corner of the video grid. Main Watermark: /usr/share/jitsi-meet/images/watermark.png Favicon: /usr/share/jitsi-meet/favicon.ico To display your community or company logo, prepare a transparent PNG image (recommended size: 166 × 48 px) and replace the default file: sudo cp /path/to/my-logo.png /usr/share/jitsi-meet/images/watermark.png Fine-tuning UI elements To adjust watermark links, application names, and toolbar buttons, open the interface configuration: sudo nano /usr/share/jitsi-meet/interface_config.js Key variables to customise include: // Customise the application name shown on browser tabs and notifications APP_NAME: 'MSCC Meet', NATIVE_APP_NAME: 'MSCC Meet', // Direct clicking the top-left logo to your organisation website JITSI_WATERMARK_LINK: 'https://www.mscc.mu', // Hide promotional banners SHOW_JITSI_WATERMARK_FOR_GUESTS: true, SHOW_WATERMARK_FOR_GUESTS: false, // Curate visible toolbar buttons to remove unused features TOOLBAR_BUTTONS: [ 'microphone', 'camera', 'closedcaptions', 'desktop', 'fullscreen', 'fodeviceselection', 'hangup', 'profile', 'chat', 'recording', 'livestreaming', 'etherpad', 'sharedvideo', 'settings', 'raisehand', 'videoquality', 'filmstrip', 'tileview', 'shortcuts', 'help' ], Note In recent Jitsi Meet releases, several user interface parameters originally located in interface_config.js (such as toolbarButtons and filmstrip controls) are being progressively consolidated directly into config.js. If you are running an updated package version or containerised deployment, verify whether your target parameter has moved into your primary config.js schema. Social media & Open Graph previews When you share a meeting URL on messaging platforms like Slack, Microsoft Teams, or WhatsApp, the client fetches Open Graph metadata to render a link preview card. Open /usr/share/jitsi-meet/title.html: sudo nano /usr/share/jitsi-meet/title.html Replace the default tags with your custom branding: <title>MSCC Meet - Monthly Code & Coffee Chat</title> <meta property=\"og:title\" content=\"MSCC Meet - Monthly Code & Coffee Chat\" /> <meta property=\"og:image\" content=\"/images/watermark.png\" /> <meta property=\"og:description\" content=\"Secure, high-quality video meetings hosted by the Mauritius Software Craftsmanship Community.\" /> <meta itemprop=\"name\" content=\"MSCC Meet\" /> Landing page footer & welcome content The landing page displays a room creation box. You can inject custom announcements, code of conduct links, or server sponsorship details below this box using welcomePageAdditionalContent.html: sudo nano /usr/share/jitsi-meet/static/welcomePageAdditionalContent.html Add your custom HTML snippet: <template id=\"welcome-page-additional-content-template\"> <div style=\"text-align: center; margin-top: 2rem; color: #fff;\"> <p>Hosted by the <strong>Mauritius Software Craftsmanship Community</strong>.</p> <p><small>Please respect our <a href=\"https://www.mscc.mu/code-of-conduct/\" target=\"_blank\" style=\"color: #4a90e2;\">Code of Conduct</a> during all sessions.</small></p> </div> </template> The upgrade dilemma Here is the catch: Jitsi Meet receives frequent software updates. When you run sudo apt-get upgrade, the jitsi-meet-web package extracts clean upstream files into /usr/share/jitsi-meet/. Without precaution, your custom watermark.png, interface_config.js, title.html, and welcome snippets are immediately overwritten by the default upstream templates. To solve this, we employ two different strategies depending on your operational preferences. Virtual locations via Nginx aliases (recommended) The most robust architectural solution is to separate your custom assets from the operating system's package directory entirely. By placing your custom assets in an independent directory (e.g. /var/www/jitsi-custom/) and using Nginx location alias directives, Nginx intercepts requests for specific assets and serves your custom files before the request ever touches /usr/share/jitsi-meet/. flowchart LR Client[\"Browser Request<br/><code>/images/watermark.png</code>\"] --> Nginx{\"Nginx Reverse Proxy\"} Nginx -->|Location Alias| Custom[\"\uD83D\uDCC1 /var/www/jitsi-custom/<br/><b>Custom Branding (Untouched by APT)</b>\"] Nginx -->|Default Fallback| Upstream[\"\uD83D\uDCC1 /usr/share/jitsi-meet/<br/><i>APT Package Files</i>\"] Create an isolated custom assets directory sudo mkdir -p /var/www/jitsi-custom/images sudo mkdir -p /var/www/jitsi-custom/static # Copy your branding assets into the safe location sudo cp /path/to/my-logo.png /var/www/jitsi-custom/images/watermark.png sudo cp /usr/share/jitsi-meet/static/welcomePageAdditionalContent.html /var/www/jitsi-custom/static/ Configure Nginx location directives Open your active Jitsi Nginx server block: sudo nano /etc/nginx/sites-available/$(hostname -f).conf Inside the server block listening on port 443, insert the specific alias overrides above the general root block: # ---------------------------------------------------- # Upgrade-Safe Custom Branding Aliases # ---------------------------------------------------- location ^~ /images/watermark.png { alias /var/www/jitsi-custom/images/watermark.png; add_header Cache-Control \"no-cache, must-revalidate\"; } location ^~ /static/welcomePageAdditionalContent.html { alias /var/www/jitsi-custom/static/welcomePageAdditionalContent.html; add_header Cache-Control \"no-cache, must-revalidate\"; } Two specific architectural choices make this block particularly resilient: Prefix Matching (^~): Using ^~ instructs Nginx that once this prefix matches, it must stop checking for any subsequent regular expression locations (~ or ~*). Jitsi Meet's default Nginx configuration includes regular expressions for routing room names; using ^~ ensures that your custom asset aliases take absolute precedence without being bypassed by upstream regex rules. Cache-Control Headers: Web browsers aggressively cache static PNG images and HTML snippets. Without explicit cache instructions, attendees may continue seeing the default Jitsi logo or outdated announcements long after you have refreshed files on disk. Adding Cache-Control \"no-cache, must-revalidate\" forces the browser to revalidate the asset with Nginx on every request. If the file has not changed, Nginx replies with a fast 304 Not Modified, giving you instantaneous visual updates without wasting network bandwidth. Test and reload Nginx sudo nginx -t sudo systemctl reload nginx Whenever apt-get upgrade updates jitsi-meet-web, it can freely replace /usr/share/jitsi-meet/images/watermark.png. Nginx will continue serving your file from /var/www/jitsi-custom/ without skipping a beat! Git time-capsule in /usr/share/jitsi-meet If you prefer modifying files directly in-place (for example, when tweaking complex CSS or JavaScript logic across multiple files), you can turn /usr/share/jitsi-meet into a local Git repository. Version control acts like an operational time-capsule: it tracks all your modifications, alerts you when an upgrade changes upstream templates, and lets you reapply your changes smoothly. Initialise the local repository Ensure git is installed, navigate to the web root, and create the baseline commit: sudo apt-get install -y git cd /usr/share/jitsi-meet sudo git init sudo git config user.name \"Jitsi Administrator\" sudo git config user.email \"admin@localhost\" # Stage the untouched upstream installation sudo git add . sudo git commit -m \"Initial upstream baseline installation\" Apply and commit custom modifications Make your changes to interface_config.js, title.html, and images, then commit them to a dedicated branch: sudo git checkout -b custom-branding sudo git add -A sudo git commit -m \"feat(branding): apply custom logos, watermarks, and welcome text\" Upgrade routine: Stash, upgrade, and pop When package upgrades arrive, use Git to preserve your modifications: cd /usr/share/jitsi-meet # Stash active customisations sudo git stash # Run the system package upgrade sudo apt-get update && sudo apt-get --only-upgrade install -y jitsi-meet-web # Commit new upstream files to repository sudo git add -A sudo git commit -m \"chore(upstream): update jitsi-meet-web package\" # Reapply custom branding branch modifications sudo git stash pop If any conflict occurs (for instance, if upstream renamed a variable in interface_config.js), Git will highlight the exact lines requiring adjustment rather than silently overwriting your work. Note Unlike the Nginx alias strategy, the Git-based approach requires manual administrator intervention during package upgrades to pop stashes and review changes. For automated deployment pipelines or servers configured with unattended-upgrades, Nginx virtual locations (Strategy A) remain strongly recommended. With upgrades in check, let's have a look at integrations with other systems. Integrate Google Calendar Coordinating virtual meetings often introduces unnecessary friction when attendees have to search through calendar invites, email threads, and messaging channels to find the correct room URL. Jitsi Meet includes native support for Google Calendar synchronisation. When enabled, your instance surfaces an interactive agenda view directly on the landing page and within user settings, allowing participants to view their schedule and enter community meetups with a single click. flowchart LR User[\"Community Member<br/>(Browser)\"] -->|1. Sign in via OAuth 2.0| GoogleAuth[\"Google Identity Services<br/>(OAuth Consent)\"] GoogleAuth -->|2. Authorised Token| User User -->|3. Read Upcoming Events| GCal[\"Google Calendar API<br/><code>calendar.events.readonly</code>\"] GCal -->|4. Parse Room URLs| JitsiUI[\"Jitsi Meet Landing Page<br/><b>1-Click Join Button</b>\"] Google Cloud project and OAuth credentials Because Part 1 of this series deployed our instance on Google Cloud Compute Engine, you already have access to the Google Cloud Console. To configure calendar synchronisation, create an OAuth 2.0 client: Enable the API: In your Google Cloud project, navigate to APIs & Services > Library, search for Google Calendar API, and click Enable. Configure the OAuth Consent Screen: Under APIs & Services > OAuth consent screen, select External (for public community members) or Internal (for Google Workspace organisations). Enter your application title (e.g. MSCC Meetup Calendar) and contact information. Add the read-only scope: https://www.googleapis.com/auth/calendar.events.readonly Generate Web Credentials: Under APIs & Services > Credentials, click Create Credentials > OAuth client ID. Application type: Web application. Name: Jitsi Meet Calendar Client. Authorised JavaScript origins: Enter your instance base URL (e.g. https://meet.mscc.mu). Authorised redirect URIs: Enter your instance OAuth callback endpoint: https://meet.mscc.mu/static/oauth.html Copy the Client ID: Google generates an alphanumeric string ending in .apps.googleusercontent.com. You do not need a client secret, as this authentication flow executes entirely in the client browser. Tip If you leave your OAuth consent screen in Testing status, Google limits authentication strictly to 100 explicitly invited Google accounts. For open community instances, transition the consent screen to In production (or add your core team members to the test user list) to avoid access errors. Adjust the Jitsi configuration Once you have your OAuth Client ID, open your server configuration file: sudo nano /etc/jitsi/meet/$(hostname -f)-config.js Locate the calendar settings section and enable the feature alongside your Google Client ID: // ---------------------------------------------------- // Google Calendar Synchronisation // ---------------------------------------------------- enableCalendarIntegration: true, // Google API OAuth 2.0 Web Client ID googleApiApplicationClientID: '1234567890-abcdefghijklmnopqrstuvwxyz.apps.googleusercontent.com', Save the file. Because config.js is fetched dynamically by clients on each page load, there is no need to restart system services. The change takes effect immediately upon hard-refreshing your browser. The welcome page and settings experience With the configuration active, participants experience two integration touchpoints: Landing Page Agenda: Below the room name input, the Calendar tab displays a \"Connect your calendar\" call-to-action. Once authenticated, Jitsi displays upcoming meetings for the day. If a calendar invite contains a Jitsi meeting URL in its location or description, a prominent Join button appears next to the scheduled time. In-Meeting Preferences: Users can also link or disconnect their calendar account at any time by opening Settings > Calendar and clicking \"Sign in with Google\". All OAuth tokens and calendar event payloads are processed and retained exclusively within the attendee's browser local storage. The Jitsi server never stores, inspects, or proxies private calendar contents, ensuring complete user privacy. Desktop client integration (Jitsi Meet Electron) In addition to web browsers, participants can connect via the official cross-platform desktop application: Jitsi Meet Electron. To ensure that the Electron desktop wrapper can frame your server without security policy violations, verify that your Nginx SSL server block includes standard frame options: add_header X-Frame-Options \"SAMEORIGIN\"; The desktop client provides hardware push-to-talk hotkeys, system tray minimisation, and native screen-sharing selectors that bypass browser permission prompts. Mobile experience When community members open a meeting link on smartphones or tablets, their experience depends heavily on how your instance handles mobile clients. By default, Jitsi Meet prompts mobile visitors with a full-screen interstitial banner urging them to install the official mobile application. Depending on your audience and operational needs, you can either streamline the in-browser WebRTC experience for friction-free joining or guide participants into the native mobile application configured for your custom domain. Seamless in-browser WebRTC If you run public webinars or developer meetups with casual attendees, forcing guests to download a mobile app creates immediate participation friction. Modern mobile browsers (such as Chrome for Android and Safari on iOS) fully support WebRTC audio, video, and screen viewing without requiring third-party software. You can suppress the promotional install interstitial and allow participants to enter meetings directly within their mobile browser. Disable mobile deep linking Open your primary configuration file: sudo nano /etc/jitsi/meet/$(hostname -f)-config.js Add or adjust the deep-linking parameters: // ---------------------------------------------------- // Mobile Browser WebRTC Experience // ---------------------------------------------------- // Disable the interstitial prompt that pushes native mobile apps disableDeepLinking: true, // In modern Jitsi Meet releases with nested configuration objects: deeplinking: { disabled: true }, Suppress promotional banners Next, disable the promotional banners in the interface configuration: sudo nano /usr/share/jitsi-meet/interface_config.js Set the mobile app promotion flag to false: // Hide the mobile app promotional banner MOBILE_APP_PROMO: false, With these settings applied, attendees tapping a meeting link on their phone land straight in the prejoin lobby or video room inside their mobile browser. Note While in-browser mobile WebRTC is convenient for guests, mobile operating systems enforce aggressive background tab management. If an attendee locks their screen or switches apps, mobile Safari and Chrome may suspend video feeds. For regular team members or moderators, the native app offers superior stability. Native mobile app configuration For recurring community members and moderators who participate frequently from mobile devices, the official Jitsi Meet Mobile App (available on iOS and Android) delivers native background audio processing, hardware battery optimisation, and incoming call notifications. However, there is an important operational pitfall to avoid: By default, the Jitsi Meet mobile app connects to the public cloud infrastructure hosted at https://meet.jit.si. If an attendee installs the app from the store and enters a room name such as MSCCMonthlyMeetup, the application connects to public Jitsi infrastructure, leaving them isolated from your self-hosted community server on meet.mscc.mu! To ensure mobile app users join your private instance: Configure the default server URL: Inside the mobile app, tap the Settings gear icon and set the Server URL field to your instance domain: https://meet.mscc.mu Once saved, all rooms created or entered from the app automatically route to your self-hosted infrastructure. Direct Joining via Deep Links: When deep linking remains enabled on your server, sharing a full meeting link (such as https://meet.mscc.mu/MSCCMonthlyMeetup) automatically prompts the phone to open the meeting inside the installed mobile app while passing your custom domain as the conference host. Retrospective note: Bare-metal VMs vs modern containers When this series was originally authored in 2020 during the rapid community pivot to remote collaboration, running standalone Debian VMs on Google Cloud Compute Engine was the standard deployment architecture for community servers. Today, many engineers opt for containerised orchestration using Docker Jitsi Meet (docker-compose). The encouraging takeaway is that the underlying principles remain identical: Rather than modifying /usr/share/jitsi-meet/, Docker deployments map persistent local directories to container volume mounts (such as ~/.jitsi-meet-cfg/web/). Rather than tweaking host Nginx configs, custom virtual locations are placed in containerised Nginx snippet directories (web/custom-snippets/). Understanding how the web client, configuration layers, and reverse proxies interact gives you total mastery over your video infrastructure, regardless of whether you deploy on bare metal or in containers. Summary and series recap Looking back at what we have covered across the series so far: Installation on GCP: Provisioning GCP Compute Engine VMs, firewall rules, and automated Let's Encrypt SSL certificates. Authentication and Security: Securing rooms with Prosody internal authentication and guest focus delegation in Jicofo. Interface Customisation and Branding: Tailoring features in config.js, branding the UI with logos and custom HTML, locking in upgrade persistence via Nginx virtual locations, and tuning client experiences for desktop and mobile devices. You now possess a private, branded, secure, and resilient video conferencing platform under your complete operational control. Share your Jitsi story What is your actual experience running or joining calls on Jitsi Meet? Did you learn the hard way about package upgrade wipeouts after a routine apt-get blew away your bespoke logos, or have you spent late nights wrestling with Nginx routing and mobile WebRTC permissions? Which unforeseen pitfalls caught you completely off guard, what ingenious hacks did you deploy to keep your community connected, and what nagging reservations still make you look twice at commercial alternatives? Do not keep those hard-earned lessons locked away on your staging box. Tell me about your setup, your triumphs, and your catastrophic misconfigurations on X (@JKirstaetter), compare notes on BlueSky (@jochen.kirstaetter.name), ping me on Mastodon (@JKirstaetter), or subscribe to the RSS Feed for more hands-on DevOps deep dives. Picture credits: Hero artwork generated with Gemini 3.1 Flash Image (Nano Banana 2) and composited with official Mauritius Software Craftsmanship Community (MSCC) branding; interface screenshots by Jochen Kirstätter. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'customise-jitsi-meet.html'; this.page.identifier = '9271ed1c97_idcustomise-jitsi-meet'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "cv.html": {
    "href": "cv.html",
    "title": "Curriculum Vitae - Get Blogged by JoKi",
    "summary": "Curriculum Vitae Copy Markdown View Markdown GitHub Markdown Source Curriculum Vitae Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'cv'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'cv'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Curriculum Vitae') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2009-12-14 - Filed under Personal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Personal data Name Jochen Michael Wolfgang Kirstätter Alias JoKi Passport photograph Date of birth 22th May 1976 Location of birth Ludwigshafen (Rhein), Germany Family status Married Parents Wolfgang Wilhelm Kirstätter Dagmar Ruth Ursula Waltraut Kirstätter, nee Obser Career summary | Experience in project and change request management, coordinating time schedules and task management for project team members. Good communication skills with customers and understanding of their business workflows. Extensive experience in software development and testing in Microsoft Visual FoxPro and knowledge in C#, including both GUI-based and web-based development with different remote database backends like Microsoft SQL Server, MySQL and PostgreSQL, in combination with either Crystal Reports or Microsoft Reporting Services. | | --- | Skills Excellent skills in designing and implementing database-driven applications. Being self-organized and goal-oriented to focus and excel given deadlines. Good skills in coordinating and parallizing project tasks. Good communication skills with customers and understanding of their business workflows. Being a team player with good leading capabilities. Work experience | 04.2009 - today | IOS Indian Ocean Software Ltd., Flic en Flac, Mauritius Senior Software Craftsman/Software Architect Task summary Supervising the software development and database department. Designing and implementing of customer-specific requirements. Technology summary Windows (8, 7, Vista, XP, 2000, Server 2012, 2008 R2, 2003, NT4) Microsoft SQL Server 2012, 2008 and 2005, MySQL 4.x and higher, PostgreSQL Microsoft Visual FoxPro (6.0 till 9.0), C# with Visual Studio (2005 till 2012) Crystal Reports (9.5 till XI) and Microsoft Reporting Services | | --- | --- | | | | | 04.1998 - today (additionally) | MiDeK - Internet Publishing, Rockenhausen-Marienthal, Germany CEO Task summary Development of a variety of different Web solutions for several customers. Mainly, dynamic data-driven Web sites based on script languages like Perl and PHP with MySQL database backend. Experience in design and implementation of scalable, multilanguage-enabled applications. Consulting and installation services on local area networks with Linux mail servers, gateways and firewalls as well as Windows Servers for small offices (approx. 5-10 clients) Technology summary Linux (Ubuntu, Debian and SuSE) and Windows (NT 4, 2000, XP, Server 2003, Server 2008, Vista and Windows 7) PHP (3.x till 5.x), some Perl knowledge, MySQL and PostgreSQL HTML, XML, XSLT, CSS, WAI 508 and other accessibility rules | | *** | | | 03.2007 - 03.2009 | ProLib Ltd., Cybercity, Mauritius Project Supervisor Task summary Supervising several project teams and coordinating communication and tasks with various Project Leads back in Germany. Responsible for design and implementation of specified requirements in given time. Analyzing local market for software development projects in banking and insurance sectors. Technology summary Windows (NT 4, 2000, XP, Server 2003, Vista and Server 2008) Microsoft SQL Server 2005 and 2008, MySQL 4.x and higher, Oracle 9.x, PostgreSQL Microsoft Visual FoxPro (6.0 till 9.0), C# with Visual Studio (2002 till 2008) Crystal Reports 10 | | | | | 06.2002 - 02.2007 (additionally) | ProLib Software GmbH, Seebruck, Germany Senior Software Developer Task summary Developing and Testing of Active FoxPro Pages 3.0 in combination with different web servers. Writing of product documentation and technical specifications. Being part-time technical supporter for customers using Active FoxPro Pages world-wide. Working as software developer in several other projects of ProLib Software GmbH. Technology summary Windows (NT 4, 2000, XP, Server 2003 and Vista) Active FoxPro Pages on Microsoft Internet Information Services (IIS), Apache HTTPd Webserver and other ISAPI capable Web servers. Microsoft Visual FoxPro (7.0 till 9.0), C#, XML, DocBook, WISE 9 | | | | | 03.1999 - 12.2006 | Kheops GmbH, Kaiserslautern, Germany (merged with ProLib Software GmbH in 2006) Software Developer Task summary Starting as Junior Software Developer and assisting in various projects on Visual FoxPro and Sybase SQL. Upgrading to Software Developer in 2001 while start working on a Medical & Surgical Management application running on Visual FoxPro 6.0 and SQL Server 2000. In 2003 working as Chief Software Developer on a distributed Tax Office Management application (VFP 8.0, SQL Server 2000 and Crystal Reports 9.5). Starting as Senior Software Developer in 2004 on a distributed Craftsman Office Management application in replication scenario (VFP 9.0, SQL Server 2000/2005 and Crystal Reports 10). Upgraded in September 2005 as Project Lead managing a team of five to eight developers, and dealing with customer requirements and change requests. All projects lasted more than one year with varying budgets between 150.000,- Euro and 800.000,- Euro. Technology summary Windows (NT 4, 2000, XP, Server 2003 and Vista) and Linux Sybase SQL Anywhere, Microsoft SQL Server 7.0, 2000 and 2005, MySQL 3.x and higher, PostgreSQL Microsoft Visual FoxPro (6.0 till 9.0), C# with Visual Studio (2002 till 2005) Knowledge in 'Mere Mortals VFP' framework and 'The Lib' and 'Acodey' component libraries. | Awards | 04.2007 | Microsoft Most Valuable Professional (MVP) Visual Developer for Visual FoxPro | | --- | --- | | 04.2006 | Microsoft Most Valuable Professional (MVP) Visual Developer for Visual FoxPro | Certificates View my Microsoft Certificates | 12.2012 | Microsoft Specialist 70-480 Programming in HTML5 with JavaScript and CSS3 | | --- | --- | | | | | 06.2004 | Microsoft Certified Professional 70-155 Designing and Implementing Distributed Applications with Microsoft® Visual FoxPro® 6.0 | | | | | 06.2004 | Microsoft Certified Professional 70-156 Designing and Implementing Desktop Applications with Microsoft® Visual FoxPro® 6.0 | Community experience | 05.2013 | Founder of Mauritius Software Craftsmanship Community (MSCC) The MSCC is the successor of the MSDUG and is a technology agnostic approach to bring any IT related people together. This is a community for those who care and are proud of what they do. For those developers, regardless how experienced they are, who want to improve and master their craft. This is a community for those who believe that being average is just not good enough. | | --- | --- | | 2012 | Beta Tester for Microsoft Windows 8, Windows Server 2012, SQL Server 2012 and Visual Studio 2012 | | | | | 03.2008 | Founder of Mauritius Software Developer User Group (MSDUG) Initiated a local user group for software developers and engineers. Monthly meetings with varying topics on object-oriented programming and database development. Mainly focused on Microsoft Windows platform but not restricted. See blog articles in category MSDUG. | | | | | 06.2007 | Beta Tester for Microsoft Windows Server 2008 and SQL Server 2008 | | | | | 12.2006 | Microsoft 'Ask The Expert' for Windows Vista Introduction roadshow on Windows Vista, Office 2007 and Exchange Server 2007 | | | | | 11.2006 | Speaker at German Developers Conference Visual FoxPro - LinkSessions on NET4COM, Writing an CLR Host and Active FoxPro Pages | | | | | 03.2006 | Beta Tester for Microsoft Windows Vista and Office 2007 | | | | | 02.2006 | Microsoft 'Ask The Expert' for SQL Server 2000/2005 \"Ready to Rock the Launch\" - Software Launch of Microsoft Visual Studio 2005, SQL Server 2005 and Biztalk Server 2006 | | | | | 11.2005 | Speaker at German Developers Conference Visual FoxPro Sessions on Subversion, Regular Expressions, Visual FoxPro running on Linux w/ wine, and essential development tools | | | | | 11.2004 | Speaker at German Developers Conference Visual FoxPro Multiple sessions on Active FoxPro Pages, and Visual FoxPro running on Linux w/ wine | | | | | 11.2003 | Speaker at German Developers Conference Visual FoxPro (as replacement for Whil Hentzen) Sessions on Visual FoxPro running on Linux w/ wine | | | | | 11.2002 | Speaker at German Developers Conference Visual FoxPro Sessions on Active FoxPro Pages | | | | | Since 11.2004 | Member of International .NET Association (INETA) Membership is based on user group activity on Microsoft .NET technology, Microsoft SQL Server and Visual FoxPro. | | | | | 03.2004 - 01.2007 | Regional director of German FoxPro User Group (dFPUG) at Speyer Monthly meetings with sessions and discussions on Microsoft Visual FoxPro, SQL Server and other programming languages. Sessions on several topics like Subversion, component development, unit testing, etc. | | | | | Since 10.2003 | Member of Microsoft CLIP | Educational experience | 09.1995 - 04.2000 | University of Technology, Kaiserslautern, Germany Chemistry science studies | | --- | --- | | | | | 08.1992 - 05.1995 | Gymnasium Weierhof, Weierhof, Germany Foreign language(s) English Grammar school | | | | | 08.1989 - 05.1992 | Realschule, Rockenhausen, Germany Foreign language(s) English, French and Spanish Secondary modern school | | | | | 08.1986 - 07.1989 | Anne-Frank-Realschule, Ludwigshafen (Rhein), Germany Foreign language(s) English and French Secondary modern school | | | | | 07.1982 - 07.1986 | Ernst-Reuter-Schule, Ludwigshafen (Rhein), Germany Elementary school | Avocational experience | 08.1995 - 01.2007 | Auxiliary fire brigade, Rockenhausen, Germany Department of Disaster Control Courses on group leader, Chemistry and Health Physics | | --- | --- | | | | | 02.1992 - 05.1998 | Auxiliary fire brigade, Marienthal, Germany Courses on voice radio, self-contained breathing/respiratory protection and squad leader | | | | Sport activities | | Running Cycling Swimming Diving - PADI Advanced Open Water Diver | | --- | --- | | | | if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'cv.html'; this.page.identifier = '9271ed1c97_idcv'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "cyclone-warning-class-3-in-mauritius.html": {
    "href": "cyclone-warning-class-3-in-mauritius.html",
    "title": "Cyclone Warning Class 3 in Mauritius - Get Blogged by JoKi",
    "summary": "Cyclone Warning Class 3 in Mauritius Copy Markdown View Markdown GitHub Markdown Source Cyclone Warning Class 3 in Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'cyclone-warning-class-3-in-mauritius'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'cyclone-warning-class-3-in-mauritius'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Cyclone Warning Class 3 in Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-01-31 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Every year around end of January... well, ok, to be honest this is my first 'full' January here on the island, but anyways the cyclons are here! Starting on the 29th January there is Cyclone Warning Class 1 that had been increased during the last 48 hours. The currrent bulletin of Mauritius Meteorological Services reads like this: CYCLONE WARNING A CYCLONE WARNING CLASS 3 IS IN FORCE IN MAURITIUS. A CYCLONE WARNING CLASS 3 IS IN FORCE IN MAURITIUS. FIFTH CYCLONE BULLETIN ISSUED AT 07H00 ON THURSDAY 31 JANUARY 2008. AT 0700 HOURS THIS MORNING, 'GULA' WAS CENTERED NEAR 18.5 DEGREES SOUTH,59.7 DEGREES EAST, THAT IS ABOUT 260 KM TO THE NORTH-EAST OF MAURITIUS. 'GULA' HAS SLIGHTLY CHANGED ITS TRAJECTORY AND IS NOW MOVING IN A SOUTH-SOUTH-WESTERLY DIRECTION WITH A SPEED OF 12 KM/H. ALONG THIS TRACK, SEVERE TROPICAL STORM 'GULA' CONTINUES TO APPROACH MAURITIUS AND IS A POTENTIAL THREAT. A CYCLONE WARNING CLASS 3 IS IN FORCE IN MAURITIUS THE PUBLIC IS ADVISED TO COMPLETE ALL PRECAUTIONS. WEATHER IN MAURITIUS WILL BE CLOUDY WITH MODERATE TO HEAVY SHOWERS,THUNDERY AT TIMES. EASTERLY GUSTS PRESENTLY OF THE ORDER OF 80 KM/H WILL INCREASE GRADUALLY. CYCLONIC CONDITIONS, THAT IS WINDS OF UP TO 120 KM/H ARE EXPECTED TO OCCUR BY MID-DAY. SEAS WILL BE PHENOMENAL. THE NEXT BULLETIN WILL BE ISSUED AT 1000 HOURS . A CYCLONE WARNING CLASS 3 IS IN FORCE IN MAURITIUS A CYCLONE WARNING CLASS 3 IS IN FORCE IN MAURITIUS ** ** And funnily that's exact the same message they are broadcasting on radio and TV. The consequences on this warning are nice for employees - stay at home and take precautions. Well, for me this is not the first cyclone. Last year at the end of February I already had the 'chance' to attend cyclone Gamede that was a big one that passed at a distance of about 250km from the west coast of Mauritius. So, I'm really interested on the impact of Gula passing on the east coast. Just in case that you like to get more information watch out these web sites: Cyclone Bulletin: https://metservice.intnet.mu/wbulmrue.htm Classification: https://metservice.intnet.mu/wcygen.htm Satellite Image: https://metservice.intnet.mu/wsatpic.htm There are even more nice pictures - satellite and trajectory - of cyclone on this site: https://www.mtotec.com/ What is a cyclone anyways? Check out the article at Wikipedia: https://en.wikipedia.org/wiki/Cyclone Alright, that's the current news from Mauritius. I'll stay at home and be online... PS: The names of cyclone for the season 2007/2008 are listed here: https://metservice.intnet.mu/wsnames.htm if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'cyclone-warning-class-3-in-mauritius.html'; this.page.identifier = '9271ed1c97_idcyclone-warning-class-3-in-mauritius'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "daily-upgrade.html": {
    "href": "daily-upgrade.html",
    "title": "Daily Upgrade - Get Blogged by JoKi",
    "summary": "Daily Upgrade Copy Markdown View Markdown GitHub Markdown Source Daily Upgrade Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'daily-upgrade'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'daily-upgrade'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Daily Upgrade') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-11 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ahhh, was eine Arbeitserleichterung! Das Auto-Update von Windows ist ja schon ganz witzig und nett, aber leider ziemlich beschränkt, da es lediglich für den Arbeitsbereich Betriebssystem und maximal Office genutzt werden kann. Und da kommt mein seit einigen Jahren geliebtes Alternativsystem wesentlich besser zum Zuge... denn dort ist prinzipiell mal alles, was verfügbar ist ad hoc aktualisierbar. Inklusive Auflösung von Abhängigkeiten und Versionen - nice service. Der Mechanismus dahinter nennt sich apt und stellt nach meinem gegenwärtigen Verständnis schon seit Jahren das Non-Plus-Ultra für automatische Updates von Debian-Systemen dar. Irgendwie ist es mir ein wenig schleierhaft, wieso dieses coole Konzept noch keinen Einzug in die achso einfache Welt der Windowsbenutzer erhalten hat. Ob's vielleicht an den vielen 'Backupkopien' des Systems liegt? Wer weiß... ich möchte es auch ehrlich gesagt nicht unbedingt wissen. Dennoch lassen sich positive Tendenzen in diesem Sektor ausmachen, da zumindest mal die Subskription von Software bei großen Downloadcenter für Free- und Shareware einen Standard ausknobeln konnten. Und darauf lässt sich garantiert aufbauen. Die technischen Möglichkeiten sind definitiv vorhanden: Dezentrale Datenhaltung Replikationsmechanismen im Push- und Pull-Verfahren Definierte XML-Dokumente für Software Administrations- und Wartungsclients für diverse Operating Systems Maintainer für Softwarepakete Insofern stellt sich die berechtigte Frage: Woran hängt's denn nun eigentlich? Ich würde extremst geniessen, wenn ich auf meinem Windows-System ebenfalls ein simples apt-get update und apt-get -u dist-upgrade absetzen könnte, und nach kurzer Zeit auf dem aktuellsten Stand der installierten Applikationen arbeiten könnte. Spassigerweise machen es wiederum andere Systeme wie etwa Cygwin vor, wie es unter Windows funktionieren könnte. Hmm, liegt's eventuell am kommerziellen Touch der Anwendungen, warum es nicht funktioniert? Und selbst wenn, dann ist es echt kein Stress entsprechende Hinweise und Abrechnungen zu integrieren. Hmm, nunja, wir werden sehen, ob vielleicht in ein paar Jahren hoffentlich etwas Vergleichbares auch unter Windows zur Verfügung steht. Es wäre absolut begrüßenswert und würde sicherlich einigen Spass bringen... Ich bin mal gespannt. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'daily-upgrade.html'; this.page.identifier = '9271ed1c97_iddaily-upgrade'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "darfs-ein-wenig-kultur-sein.html": {
    "href": "darfs-ein-wenig-kultur-sein.html",
    "title": "Darf's ein wenig Kultur sein? - Get Blogged by JoKi",
    "summary": "Darf's ein wenig Kultur sein? Copy Markdown View Markdown GitHub Markdown Source Darf's ein wenig Kultur sein? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'darfs-ein-wenig-kultur-sein'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'darfs-ein-wenig-kultur-sein'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Darf's ein wenig Kultur sein?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-07 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Und wieder ein erfolgreicher Workshop-Tag neigt sich dem Ende. Spannenderweise wurden wir heute auf Grund einer Bombenentschärfung umquartiert. Gleich wieder 'diese Hektik' am frühen Morgen... \uD83D\uDE01 - da wir ein wenig früher als gestern begonnen haben, starteten wir dennoch zeitig und wieder einmal sehr umfangreich. Nach den Grundlagen und den ersten Programmierschritten mit den Active FoxPro Pages haben wir heute uns mit den fortgeschritteneren Themen, wie Applikationskonfiguration, Session-Management und weiteren Fähigkeiten der AFP beschäftigt. Zwischendurch immer mal wieder den ein oder anderen Exkurs ins Unterholz des Fuchs oder des Internets (HTML/XML). Insgesamt hoffentlich wieder ein gelungener Tag. Auf Grund der Nähe zu Sans Souci liessen wir den arbeitsreichen Tagesverlauf unternahmen wir einen gemeinsamen Entspannungsspaziergang in der riesigen Parkanlage. Es fasziniert mich immer wieder aufs Neue in welchem Umfang aber auch Detailgrad die Anlage insgesamt und die Gebäude im Besonderen angelegt oder gebaut wurden. Ergänzend durch die Informationen über die Situation vor der Öffnung der Mauer - 'genau dort hinter den Kolonaden verlief die Mauer.' - freut es mich umso mehr, dass wir in Deutschland (noch) solch imposante Sehenswürdigkeiten für die Welt zu offerieren haben. Sicherlich auch ein Urteilsgrund für die UNESCO Potsdam den Status eines Weltkulturerbe zu geben. Well, nach so viel Arbeit und Kultur meldet sich selbstverständlich der Magen. Diesmal blieben wir direkt im Hotel. Da einige der Kursteilnehmer hier ebenfalls untergebracht sind, nutzten wir die Gelegenheit für eine Art 'VFP-Stammtisch' in kleiner Runde - zumindest anfangs. \uD83D\uDE0E Vielleicht ergibt sich zukünftig ja die Situation, dass ein weiterer Stammtisch im Rahmen der dFPUG gegründet wird. Würde mich und sicherlich auch andere freuen... Mal sehen, nächste Woche gibt's einen weiteren Workshop - Acodey Komponentenbibliothek. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'darfs-ein-wenig-kultur-sein.html'; this.page.identifier = '9271ed1c97_iddarfs-ein-wenig-kultur-sein'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "decorator-im-praktischen-einsatz.html": {
    "href": "decorator-im-praktischen-einsatz.html",
    "title": "Decorator im praktischen Einsatz - Get Blogged by JoKi",
    "summary": "Decorator im praktischen Einsatz Copy Markdown View Markdown GitHub Markdown Source Decorator im praktischen Einsatz Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'decorator-im-praktischen-einsatz'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'decorator-im-praktischen-einsatz'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Decorator im praktischen Einsatz') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-02 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ja, ich weiß... Decorator hier, Decorator dort. \uD83D\uDE01 - Langsam reicht es doch mal wieder. Nun, ja ihr habt ja recht. Nach den bisherigen Blogeinträgen Design Pattern: Decorator Anmerkungen zum Decorator Design Pattern möchte ich die Serie über dieses Entwurfsmuster mit diesem Beitrag vorerst abschliessen. Warum der Nachschlag? Wieder einmal die Unterhaltung mit Olaf Doschke im Forum der dFPUG. Es ist sehr anregend mit anderen Entwicklern über die Thematik und Implementierung zu sprechen. Olaf hatte noch sehr gute Anregungen für die Realisierung und Verbesserung des bisherigen Quellcodes. Zu meiner Entschuldigung möchte ich aber auch sagen, dass der Einstiegsartikel nicht mit den Details des Fehlerhandlings 'langweilen' sollte, sondern primär die Anwendbarkeit des Entwurfsmusters verdeutlichen. Ich hab mir auch überlegt, daß das ganze Problematisch werden kann, wenn oDecorated ein COM-Objekt ist. Dann läßt sich nämlich PEMSTATUS() nicht unbedenklich anwenden, da müßte dann wohl Ersatzweise eher AMEMBERS und ASCAN ran, wenn man es denn so umstellen wollte. Ein sehr guter Einwand übrigens. Den habe ich zwar an anderen Stelle schon berücksichtigt, aber noch nicht in Verbindung mit dem Decorator, da ich noch nicht in Versuchung kam, einen COM-Server zu dekorieren. Aber in der Tat, ein sehr gutes Argument. Ich fände es übrigens völlig in Ordnung, wenn der Zugriff auf Protected-Eigenschaften/Methoden in die Hose geht, das wäre ja auch genau das Verhalten, was das Originalobjekt an den Tag legen würde. Well, das ist eine Sache der Herangehensweise. Ich würde in so einem Falle lediglich einen ASSERT absetze, so dass eine Info für den Entwickler zur Verfügung steht, aber die Anwendung keinen Ausstieg provoziert. Hm, effektiv sollte man das eh komplett in einen Try..EndTry packen, dann ist man sicherlich etliche Sorgen los. Als Alternative verwende ich ihm nachfolgenden Code einen (Nach-)Lade-Mechanismus, falls die Eigenschaft nicht existieren sollte. Ich weiß, dass das nicht die feine englische Art ist, aber auf diese Weise provoziere ich erstens keinen Absturz und zweitens bietet mir dieser Ansatz einen einfachen Cache. Vorgesetzt natürlich, dass die Methode GetToken() in der jeweiligen Ableitung auch entsprechend implementiert ist. Wenn wir also die bisherigen Erkenntnisse zusammenpacken, dann sollte unser Quellcode in etwa so aussehen: *============================================================ * Interface definition for decorator classes *============================================================ Define Class IDecorator As Relation oObject = .Null. Function Init(toObject As Object) As Boolean Return .T. EndFunc Function Destroy() As Boolean Return .T. EndFunc Function SetClient(toObject As Object) As VOID EndFunc Protected Function This_Access(tcPem As String) As Object Return This EndFunc Function GetToken(toObject As Object, tcToken As String) As Variant Return .Null. EndFunc EndDefine *============================================================ * 'Abstract' base class for Decorator Design Pattern *============================================================ Define Class AbstractDecorator As IDecorator Function Init(toObject As Object) As Boolean Return This.SetClient(m.toObject) EndFunc Function Destroy() As Boolean This.oObject = .Null. EndFunc Function SetClient(toObject As Object) As VOID If Pcount() == 1 .And. ; Vartype(m.toObject) == \"O\" This.oObject = m.toObject EndIf EndFunc Function This_Access(tcPem As String) As Object Local loReturn, luValue, laMembers[1] m.loReturn = This m.luValue = .Null. If Vartype(This.oObject) == \"O\" AMembers(m.laMembers, This.oObject, 0, \"GU\") If (PemStatus(This.oObject, m.tcPem, 5) .And. ; Not PemStatus(This.oObject, m.tcPem, 2)) .Or. ; Ascan(m.laMembers, m.tcPem, 1, -1, 1, 1+2+4) > 0 m.loReturn = This.oObject Else If Not PemStatus(This, m.tcPem, 5) Assert .F. Message \"PEM: '\" + m.tcPem + \"' not available.\" AddProperty(This, m.tcPem, .F.) m.luValue = This.GetToken(This, m.tcPem) Store m.luValue To (\"This.\" + m.tcPem) EndIf EndIf EndIf Return m.loReturn EndFunc Function GetToken(toObject As Object, tcToken As String) As Variant Return .Null. EndFunc EndDefine Übrigens nicht erschrecken, ob der vorangestellten Interface-Definiion, aber auf diese Weise mache ich mir das Leben ein wenig einfacher, falls ich mal was suchen sollte. \uD83D\uDE01 Wie schon geschrieben, ist die Methode GetToken eigentlich überflüssig, aber dennoch ganz nett. Als beispielhaftes Einsatzgebiet möchte euch mal eine Ableitung des Decorators für Mehrsprachigkeit zeigen. Ich nehme an, dass der 'Nutzen' von GetToken klarer erscheinen dürfte, insbesondere warum ich von einem einfachen Cache spreche: *============================================================ * Concrete class definition for encapsulated language handling. * This class is referred as object 'Language' in AfpWiki. *============================================================ Define Class LanguageDecorator As AbstractDecorator AfpWiki = \"AfpWiki - A wiki engine based on Active_FoxPro_Pages\" *------------------------------------------------------------ * Try to gather information from somewhere else. *------------------------------------------------------------ Function GetToken(toObject As Object, tcToken As String) As Variant Local lcReturn *--- This could be any method on the decorated object. *--- ie. m.lcReturn = This.oObject.MsgSvc(m.tcToken) *--- m.lcReturn = This.oObject.QueryXml(m.tcToken) *--- m.lcReturn = This.oObject.SqlExecute(This.oObject.nHandle, m.tcToken) m.lcReturn = This.oObject.GetToken(This, m.tcToken) If Empty(m.lcReturn) .Or. IsNull(m.lcReturn) m.lcReturn = This.oObject.Language + \": '\" + m.tcToken + \"' not definied\" EndIf Return m.lcReturn EndFunc EndDefine Zur Erläuterung: Sofern eine Eigenschaft nicht existieren sollte, können wir potentiell mittels GetToken eine externe Quelle nach dem Wert befragen. Dabei kann es sich um alle mögliche Arten von Quelle handeln - Web Service, Datenbank, XML-Datei, etc.. Den ermittelten Wert legen wir dann in die neu erzeugte Eigenschaft ab, und erreichen hierdurch den Cache-Effekt. Bei einer weiteren Anfrage auf die gleiche Eigenschaft können wir bereits die Information bieten und sparen uns den Remotezugriff auf unsere externe Quelle. Falls wir dennoch keine passable Information erhalten sollen, geben wir eben einen Defaultwert aus. Ich verwende diesen Decorator wie gesagt für die Abbildung von Mehrsprachigkeit - pro Sprache eine Instanz des Decorators -, und habe dabei den Effekt, dass ich während der Laufzeit sofort erkennen kann, wo meine Übersetzungen bspw. noch lückenhaft sind. Im übrigen wäre es denkbar, dass GetToken einen Fail-Safe auf die Standardsprache machen könnte... So, der Vollständidkeit wegen hier noch die Klassendefinition der Sprachklasse: *============================================================ * 'Abstract' base class for all languages. * This class defines the interface. *============================================================ Define Class AbstractLanguage As Relation Language = \"\" Function GetToken(toObject As Object, tcToken As String) As Variant Return \"\" EndFunc EndDefine *------------------------------------------------------------ * German translations *------------------------------------------------------------ Define Class LanguageDE As AbstractLanguage Of \"abstract.prg\" Language = \"de\" CaptionBackup = \"Sicherung oder Wiederherstellung des AfpWiki\" CaptionBuild = \"Erstellung AfpWiki\" CaptionCategory = \"Kategorische Ansicht\" CaptionConnectionError = \"Verbindungsfehler zur Datenbank\" CaptionEdit = \"Bearbeiten von \" CaptionEmpty = \"Neuer Eintrag \" Function GetToken(toObject As Object, tcToken As String) As Variant Local lcReturn m.lcReturn = This.Language + \": '\" + m.tcToken + \"' nicht definiert\" Return m.lcReturn EndFunc EndDefine Ups, nun habt ihr fast die vollständige Implementierung des Language-Handlings vom AfpWiki... \uD83E\uDD2A if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'decorator-im-praktischen-einsatz.html'; this.page.identifier = '9271ed1c97_iddecorator-im-praktischen-einsatz'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "der-marathon-beginnt.html": {
    "href": "der-marathon-beginnt.html",
    "title": "Der Marathon beginnt... - Get Blogged by JoKi",
    "summary": "Der Marathon beginnt... Copy Markdown View Markdown GitHub Markdown Source Der Marathon beginnt... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'der-marathon-beginnt'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'der-marathon-beginnt'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Der Marathon beginnt...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-11-10 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Der Startschuss für die 11. Entwicklerkonferenz ist offiziell gefallen. Und der Pre-Conference Tag hatte es wortwörtlich in sich. Im Laufe des Tages wurden in drei unterschiedlichen Thementracks aktuelle Informationen zu Visual FoxPro vermittelt. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'der-marathon-beginnt.html'; this.page.identifier = '9271ed1c97_idder-marathon-beginnt'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "design-pattern-decorator.html": {
    "href": "design-pattern-decorator.html",
    "title": "Design Pattern: Decorator - Get Blogged by JoKi",
    "summary": "Design Pattern: Decorator Copy Markdown View Markdown GitHub Markdown Source Design Pattern: Decorator Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'design-pattern-decorator'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'design-pattern-decorator'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Design Pattern: Decorator') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-12-03 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Okay, heute mal wieder einen technischen Eintrag zu den Möglichkeiten der Umsetzung von Design Patterns in Visual FoxPro. Der Eintrag erläutert die Implementierung des Decorator (deutsch: Dekorierer). Schauen wir uns zuerst die Definition von Wikipedia an: Die Instanz eines Dekorierers wird vor die zu dekorierende Klasse geschaltet. Der Dekorierer hat die gleiche Schnittstelle wie die zu dekorierende Klasse. Aufrufe an den Dekorierer werden dann verändert oder unverändert weitergeleitet (Delegation), oder sie werden komplett in Eigenregie verarbeitet. Der Dekorierer ist dabei \"unsichtbar\", da der Aufrufende gar nicht mitbekommt, dass ein Dekorierer vorgeschaltet ist. VFP bietet hier einen interessanten Lösungsansatz über die '_Access'-Funktionalität an. Es handelt sich dabei im eigentlichen Sinne um einen 'get'-Mechanismus, wie man es beispielsweise von Java respektive C# kennt. In VFP kann man nun eine Funktion namens 'This_Access' schreiben, welche permanent bei einem Zugriff auf das Objekt evaluiert wird. Es handelt sich dabei um einen impliziten Mechanismus, der ideal für das Decorator Design Pattern geeignet ist. Wichtig ist, dass man immer bei This_Access ein Objekt als Rückgabetyp liefert. Schliesslich würde 'This' immer einem Objekt entsprechen. Nun, unter Berücksichtigung dieser Annahmen implementieren wir mal einen ersten Entwurf für den Decorator: *========================================================================== Define Class Decorator As Session OlePublic *========================================================================== DataSession = 1 *--- Die Eigenschaft des Dekorierten kann, muss aber nicht, auf *--- Protected gesetzt werden. Es kommt ganz darauf an, ob man *--- dem Interface den Zugriff auf das 'interne' Objekt gestattet *--- oder nicht. Bei den OLEClass-Klassen von VFP steht das Decorated- *--- Objekt in der Eigenschaft This.Object zur Verfügung. *--- Protected oDecorated oDecorated = .Null. *--- Wir bieten zwar die Möglichkeit an, dass das dekorierte Objekt *--- direkt bei der Instanzierung aggregiert bzw. referenziert werden *--- kann; müssen jedoch berücksichtigen, dass das COM-Interface keine *--- Parameters akkzeptiert. Daher gibt's additiv die Methode *--- This.SetDecorated() zur Erfüllung der gleichen Aufgabe. Function Init( toDecorated As Object ) As Boolean This.oDecorated = toDecorated Endfunc Function Destroy() As Boolean This.oDecorated = .Null. Endfunc *--- Setzt das zu dekorierende Objekt zur Laufzeit. Über diese *--- Methode können auch multiple Objekte geswitched werden. Wir *--- haben also die Möglichkeit zum 'Recycling' des Decorators. Function SetDecorated( toDecorated As Object ) As VOID ; HelpString \"Setzt das zu dekorierende Objekt des Decorator. Diese Methode muss bei COM-Zugriff genutzt werden.\" This.oDecorated = .Null. This.oDecorated = toDecorated Endfunc *--- Eigentliche Funktionalität des Decorators. Über die VFP-Funktionalität *--- von \"_Access\" kann der selektive Zugriff auf den Decorator bzw. dessen *--- zu dekorierendes Objekt reguliert werden. Die Arbeitsweise sieht so aus, *--- Zugriffe wie etwa 'This.Width' je nach Existenz der Eigenschaft 'Width' *--- zuerst am Decorator getestet werden und dann an das interne Objekt geleitet *--- werden. Function THIS_ACCESS( tcMember As String ) As Object; HelpString \"Ermöglicht den geswitchten Zugriff auf das eigene bzw. zu dekorierende Objekt.\" If Pemstatus(This, Upper( tcMember ), 5 ) Return This Else Return This.oDecorated Endif Endfunc Enddefine Sieht doch schon recht vernünftig und brauchbar aus, oder? Effektiv ist unser Decorator damit vollständig implementiert und wir können uns neuen Ufern zuwenden... \uD83D\uDE0E Wichtig bei der obigen Implementierung ist, dass die Eigenheiten des COM-Interface direkt berücksichtigt werden. Ich nenne es mal ein Dual-Interface für VFP-internen Nutzen und COM-Nutzen. Selbstverständlich kann man das COM-Interface noch mittels entsprechender _COMATTRIB-Definitionen vernünftig ausformulieren, aber diesen Task überlasse ich euch als Hausaufgaben. So, wozu kann man den Decorator überhaupt nutzen? Nun, die Einsatzmöglichkeiten sind vielfältig. Dennoch wird der Decorator vorrangig zur Erweiterung von Objekten genutzt, sofern keine Ableitungen gebildet werden können. Das ist gerade im Zusammenhang mit ActiveX-Controls (siehe auch VFP OleClass und OleClassBound) sowie Klassen dritter Anbieter interessant. Aber auch zur Erweiterung von Standard-Objekten in VFP eignet sich der Decorator hervorragend. Ich möchte euch mal exemplarisch die Nutzung mit dem Scatter-Objekt zeigen. Also, das Scatter-Objekt zur objektbasierten Repräsenz eines Datensatz hat ja schon einige Vorzüge, aber IMHO fehlt etwas extrem wichtiges: Methoden. Das Scatter-Objekt besitzt lediglich Eigenschaften gemäß den Feldern des genutzten Alias, aber keinerlei Methoden. Und hier kommt nun das Decorator-Ppattern ins Spiel. Statt der direkten Verwendung des Aufruf Scatter Memo Name loScatter Nutzt man eine Ableitung des Decorator mit der gleichen respektive erweiterten Funktionalität. Hier zunächst mal die Klassendefinition des 'ScatterDecorator': *========================================================================== Define Class ScatterDecorator As Decorator *========================================================================== Procedure Scatter Scatter Name This.oDecorated Memo EndProc Procedure Gather Gather Name This.oDecorated Memo EndProc EndDefine That's all folks. Einfach eine Ableitung unserer abstrakten Decorator-Klasse und Methoden für die beiden Befehle Scatter und Gather erzeugt. Bereits jetzt haben wir eine identische Repräsentanz eines nativen Scatter-Objekts in einem Decorator - das Interface ist identisch! Okay, aber welchen echten Nutzen haben wir über diesen Weg? Ganz einfach... Unsere Klasse ScatterDecorator können wir beliebig mit weiteren Methoden erweitern. Ich nutze beispielsweise gerne eine Methode namens 'This.IsNew', die mir direkt Auskunft darüber gibt, ob ein Datensatz einen bestimmten Zustand inne hat. Oder denkbar wären weitere Informationen über die Tabellenstruktur, auf der das ScatterDecorator-Objekt basiert: This.TableAlias(), This.PrimaryKey(), This.Copy, This.Blank(), This.Next(), This.Previous(), etc... Ich hoffe, dass die Idee rübergekommen ist. Schauen wir uns nun mal die ScatterDecorator-Klasse im Einsatz an: *========================================================================== * Sample for ScatterDecorator *========================================================================== Use (Home(0) + \"foxcode.dbf\") Locate For Abbrev = \"SCAN\" oRec = Createobject('ScatterDecorator') oRec.Scatter() Wait Window oRec.Data && This works fine! Scatter Name oScatter Wait Window oScatter.Data Dimension myArr[3] myArr[1] = Createobject('ScatterDecorator') myArr[1].Scatter Wait Window myArr[1].Data && This fails with \"Property 'MemoField' is not found\" oRec2 = myArr[1] Wait Window oRec2.Data && This works fine! Wie ihr sehen könnt, besteht keinerlei Unterschied zwischen den Ausgaben von oRec.Data und oScatter.Data. Der Decorator leitet die Aufrufe direkt durch und produziert daher das gleiche Resultat. Kleiner Nebeneffekt: Man spart sich die ganzen Optionen des Scatter-Befehls - \uD83D\uDE01 - Diese sind in der Klassendefintion des Decorators hinterlegt. Achja, nur so als Anregung... Ist doch ziemlich cool, wenn man dem Empty-Objekt ebenfalls Methoden verpassen kann, oder? Ich kann mir ehrlich gesagt nicht vorstellen, dass ihr bisher noch nie in die Situation gelaufen seid, dass man zwar ein Empty-Objekt nutzen will, sofern es Methoden besitzen würde... Oder? Auch hier ist das Decorator Design Pattern optimal geeignet: *========================================================================== Define Class EmptyDecorator As Decorator *========================================================================== *--- Überlagerung der Init-Methode, da wir direkt mit einem *--- Empty-Objekt als dekoriertem Objekt starten wollen. Falls *--- also kein Objekt reinkommt, erzeugen wir uns selbst eins. Function Init( toDecorated As Object ) As Boolean Local loDecorated m.loDecorated = m.toDecorated If Pcount() == 0 Or Not Vartype( m.loDecorated ) == 'O' m.loDecorated = CreateObject(\"Empty\") EndIf Return Dodefault( m.loDecorated ) EndFunc Function IsValid() As Boolean * Irgendwelche Prüfungen... EndFunc Function AddProperty( tcMember As String, tuValue As Variant ) As Boolean Return AddProperty( This, tcMember, tuValue ) EndFunc Die Implementierung für einen EmptyDecorator ist sicherlich kontextspezifisch. Dennoch ergeben sich über diesen Ansatz weitere Freiheitsgrade in der Programmierung und vorallem effizienteren Nutzung von 'leeren' Objekten. Und man umgeht eine leidige Unzulänglichkeit von VFP: Aktuell ist keine Ableitung von Empty möglich: *========================================================================== Define Class Transfer As Empty *========================================================================== Das ist leider nicht möglich, aber bei der Nutzung des EmptyDecorator kommen wir dennoch in den gewünschten Bereich: *========================================================================== Define Class Transfer As EmptyDecorator *========================================================================== Yeah, let's rock the code... Ich hoffe, dass ich euch eine Idee über den theoretischen Aufbau, die Implementierung und den technischen Nutzen des Decorators in diesem Artikel vermitteln konnte. Mein Fazit in diesem Zusammenhang ist, dass dieses Pattern immens Vorteile bringen kann und bereits integrierter Bestandteil von Visual FoxPro - OleClass und OleBoundClass - ist. Sobald man also ActiveX-Controls in seinem Projekt benutzt, verwendet man bereits einen Decorator. \uD83D\uDE0E Über ein bisschen Feedback würde ich gerne freuen. Für welchen Einsatzzweck nutzt ihr einen Decorator oder würdet ihn nutzen? Bis denne, JoKi PS: Ich muss die Klassen noch ein wenig ausformulieren, aber dann stehen diese als Attachment hier zur Verfügung. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'design-pattern-decorator.html'; this.page.identifier = '9271ed1c97_iddesign-pattern-decorator'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "design-pattern-singleton.html": {
    "href": "design-pattern-singleton.html",
    "title": "Design Pattern: Singleton - Get Blogged by JoKi",
    "summary": "Design Pattern: Singleton Copy Markdown View Markdown GitHub Markdown Source Design Pattern: Singleton Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'design-pattern-singleton'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'design-pattern-singleton'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Design Pattern: Singleton') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-04 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Als Konsequenz der Anwendung im Beitrag Scripting.FileSystemObject stelle ich euch hier die Theorie des Design Pattern Singleton (deutsch: Einzelstück) und meine interpretierte Implementierung vor. In Wikipedia ist die Definition wie folgt beschrieben: *Das Einzelstück (engl. Singleton) ist ein in der Softwareentwicklung eingesetztes Entwurfsmuster und gehört zur Kategorie der Erzeugungsmuster (engl. Creational Patterns). Es stellt sicher, dass zu einer Klasse nur genau ein Objekt erzeugt werden kann und ermöglicht einen globalen Zugriff auf dieses Objekt. [...] Das Einzelstück findet Verwendung, wenn nur ein Objekt zu einer Klasse existieren darf und ein einfacher Zugriff auf dieses Objekt benötigt wird oder wenn das einzige Objekt durch Unterklassenbildung spezialisiert werden soll. Anwendungsbeispiele sind: Ein zentrales Protokoll-Objekt, das Ausgaben in eine Datei schreibt. Druckaufträge, die zu einem Drucker gesendet werden, sollten nur in einen einzigen Puffer geschrieben werden.* Wichtig finde ich hier, dass der Singleton die Sicherstellung der einmaligen Objekterzeugung von anderen Klassen verwaltet. Es heißt nicht zwangsläufig, dass der Singleton nur einmalig instanziiert werden kann. Wobei das natürlich das Optimum darstellen würde. Soviel zur Theorie, schauen wir uns das Pattern mal in der rauen VFP-Praxis an. Vor allem auch die Frage wo wir sowas etwas überhaupt gebrauchen könnten... Im Beitrag Scripting.FileSystemObject bin ich leicht auf das Thema Applicationservices eingegangen (nicht im Zusammenhang mit SOA), und habe für den einfachen Zugriff einen zentralen Anlaufpunkt in meiner Anwendung erzeugt, welcher mir Objektreferenzen liefert. Wichtig ist hierbei, dass der Dienst im Normalfall nur ein einziges Mal in der Anwendung existiert und dass ich mich nicht um die Erstellung des Dienstes kümmern muss. Und genau für diesen Einsatzzweck coden wir nun unser Singleton live und in Farbe... Als Basisklasse zur Verwaltung der Objekte verwenden wir eine Collection und spendieren dieser noch ein paar Funktionen: *==================================================================== * Beispielimplementierung für Singleton Design Pattern *==================================================================== Define Class AbstractSingleton As Collection *------------------------------------------------------------------ * Liefert Objektreferenz zum spezifizierten Token * Falls das Objekt noch nicht vorliegt, wird es erzeugt * und der Collection hinzugefügt. *------------------------------------------------------------------ Function GetReference(tcToken As String) As Object Local loReturn m.loReturn = .Null. *------------------------------------------------------------------ * Assertions und defensive Programmierung... *------------------------------------------------------------------ Assert Vartype(m.tcToken) == T_CHARACTER && \"C\" With This If .Exist(m.tcToken) m.loReturn = .Item(m.tcToken) Else m.loReturn = .createInstance(m.tcToken) If Vartype(m.loReturn) == T_OBJECT .Add(m.loReturn, Transform(m.tcToken)) EndIf EndIf EndWith Return m.loReturn EndFunc *------------------------------------------------------------------ * Diese Methode erzeugt das eigentliche Objekt, * welches der Singleton als Referenz zurück gibt. *------------------------------------------------------------------ Protected Function createInstance(tcToken As String) As Object Return .Null. EndFunc EndDefine *==================================================================== * Konkretisierte Ableitung für Dienste. *==================================================================== Define Class ServiceSingleton As AbstractSingleton Protected Function createInstance(tcToken As String) As Object Local loReturn, loException, lcClass, lcLibrary m.loReturn = .Null. m.lcClass = \"\" m.lcLibrary = \"\" Try *--- Hier kann man beliebigen Code zur Übersetzung *--- des Token auf Class und ClassLibrary implementieren *--- Beliebte Varianten sind: *--- Do Case *--- Lookup auf Tabelle *--- Lookup auf XML-Konfiguration *--- Verwendung des Design Pattern: Factory If Lower(m.tcToken) == \"filesystemobject\" m.lcClass = m.tcToken EndIf m.loReturn = NewObject(m.lcClass, m.lcLibrary) Catch To loException m.loReturn = .Null. Assert .F. Message m.loException.Message EndTry Return m.loReturn EndFunc EndDefine Das war's auch schon. Der Singleton schaut beim Aufruf der GetReference-Methode zuerst in seiner Collection nach, ob bereits eine Objektreferenz für das Token vorhanden ist oder nicht. Bei negativer Prüfung erzeugt der Singleton die Instanz selbst, speichert die Referenz intern und liefert sie an den Aufrufenden zurück. Wie im Quellcode bereits kommentiert, kann die Implementierung der geschützten Methode im Prinzip beliebig unterschiedlich ausfallen. Dabei spielt es auch keine Rolle, ob man Instanzen von VFP-Klassen, COM-Servern oder etwa Web Services erzeugt. Der Singleton muss nur wissen er eine einzige Instanz produzieren kann, der Rest funktioniert dann automagical. Im weiteren Verlauf der Verwendung braucht sich der Entwickler dann keine weiteren Gedanken mehr machen. Leider oder zum Glück fehlt Visual FoxPro das Konzept von statischen Klassen (static). Über dieses Sprachkonstrukt wäre man sehr wohl in der Lage die Einmaligkeit einer Klasseninstanz zu gewährleisten. Hm, dabei fällt mir gerade ein, dass es seit einiger Zeit ja den Befehl AInstance() gibt. Durch Integration in die Init-Methode des Singleton können wir dann doch wiederum was in der Art wie \"static\" programmieren: Define Class AbstractSingleton As Collection *------------------------------------------------------------------ * Sicherstellen, dass der Singleton nur einmalig existiert. *------------------------------------------------------------------ Function Init() As Boolean Local lnCount, laInstances[1] m.lnCount = AInstance(m.laInstances,This.Class) Return (m.lnCount < 1) EndFunc *... EndDefine Nicht ganz fein, aber es funktioniert. Auf diese Weise stellen wir sicher, dass unser Singleton selbst nur einmal existiert und dass er immer nur eine Referenz von angefragten Objekten erzeugt und liefert. Ich hoffe, dass ich euch mit dem hier gezeigten Code und den Erklärungen des Design Pattern Singleton ein wenig plausibel machen konnte. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'design-pattern-singleton.html'; this.page.identifier = '9271ed1c97_iddesign-pattern-singleton'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "design-pattern-strategy.html": {
    "href": "design-pattern-strategy.html",
    "title": "Design Pattern: Strategy - Get Blogged by JoKi",
    "summary": "Design Pattern: Strategy Copy Markdown View Markdown GitHub Markdown Source Design Pattern: Strategy Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'design-pattern-strategy'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'design-pattern-strategy'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Design Pattern: Strategy') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-04 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Heute denken wir uns einmal eine Strategie aus, wie wir Problemstellungen in unseren Anwendungen schneller, flexibler und einfacher lösen können. Dieser Artikel beschreibt die exemplarische Implementierung des Entwurfsmuster Strategie (Strategy) in Visual FoxPro. Schauen wir uns zunächst einmal wieder die Definition aus der Wikipedia an: Strategie-Objekte werden ähnlich wie Klassenbibliotheken verwendet. Im Gegensatz dazu handelt es sich jedoch nicht um externe Programmteile, die als ein Toolkit genutzt werden können, sondern um integrale Bestandteile des eigentlichen Programms, die deshalb als eigene Objekte definiert wurden, damit sie durch andere Algorithmen ausgetauscht werden können. Meistens wird eine Strategie durch Klassen umgesetzt, die eine bestimmte Schnittstelle implementieren. In Sprachen wie Smalltalk, in denen auch der Programmcode selbst in Objekten abgelegt werden kann, kann eine Strategie aber auch durch solche Code-Objekte realisiert werden. *Die Verwendung von Strategien bieten sich an, wenn viele verwandte Klassen sich nur in ihrem Verhalten unterscheiden, unterschiedliche, austauschbare Varianten eines Algorithmus benötigt werden, Daten innerhalb eines Algorithmus vor Klienten verborgen werden sollen oder verschiedene Verhaltensweisen innerhalb einer Klasse fest integriert (meist über Mehrfachverzweigungen) sind und die verwendeten Algorithmen wiederverwendet werden sollen oder die Klasse flexibler gestaltet werden soll.* Hat jemand was verstanden? Ich hab's auf den ersten Blick ehrlich gesagt nicht kapiert und mir daher mal verschiedene Gedanken über den obigen Inhalt gemacht. Dabei bin ich von der Situation viele verwandte Klassen sich nur in ihrem Verhalten unterscheiden aus gestartet. Okay, also viele verwandte Klassen bedeutet zunächst, dass eine abstrakte Klasse und zig Ableitungen davon gibt, die alle über die gleiche Schnittstelle verfügen. Soweit ja noch klar. Damit können wir sicherstellen, dass die einzelnen Klassen austauschbar werden und wir erhalten Flexibilität und Wiederverwendbarkeit. Jetzt brauchen wir noch irgendetwas, welches wir als Ansprechpartner innerhalb unserer Anwendung nutzen können. Diese Komponente trägt dann auch dafür Sorge, dass die jeweilige Instanz des gewünschten Verhaltens verwendet wird. Wir müssen uns also nicht mehr drum kümmern, sondern unterhalten uns nur mit dieser Komponente. Somit kapseln wir die einzelnen Strategien vom Klienten. Gut, damit ergeben sich für uns folgende Erkenntnisse: abstrakte Klasse für die Schnittstelle (abstrakte Strategie) beliebige Anzahl konkreter Klassen mit dem Algorhythmus (konkrete Strategie) einen Ansprechpartner für unsere Aufrufe (Kontext) und unsere Anwendung (Klient) Unsere Anforderung Der nachfolgende Beispielcode geht davon aus, dass wir ein Exportmodul für unsere Anwendung erstellen wollen. Wir haben einen Cursor und wollen diesen in unterschiedliche Formate exportieren. Das heißt, wir haben unterschiedliche Ausgabeformate für den gleichen Input und wollen dies auf flexible Art und Weise lösen. Das Beispiel selbst ist sehr einfach gehalten und steht in Analogie zum Befehl Copy To in VFP, aber auch weiteren Exportmöglichkeiten. Abstrakte Strategie Als erstes erstellen wir uns unsere Klassendefinition für alle Exportvarianten: *//- Abtrakte Basisklasse für alle Exporttypen (abstrakte Strategie) Define Class AbstraktExport As Custom && Abstract Strategy cDateierweiterung = \"\" cVerzeichnis = \"\" cDatei = \"\" cBeschriftung = \"\" Function Schreiben() As Boolean EndFunc EndDefine Ganz einfach gehalten. Es gibt ein Informationen zur Ausgabedatei und die eigentliche Methode zum Erzeugen der Ausgabe. Konkrete Strategie(n) Wir leiten uns hier für jeden Exporttypen, den wir anbieten wollen, eine eigene Klasse von unserer abstrakten Strategie ab und implementieren den Code in der Methode schreiben. Wir wollen folgende Formate anbieten: CSV, XML und Excel. Dazu definieren wir uns drei Klassen: *--- Export als kommaseparierte Liste (CSV) Define Class ExportCsv As AbstraktExport && (konkrete Strategie) cDateierweiterung = \".txt\" cBeschriftung = \"Export als kommaseparierte Liste\" Function Schreiben() As Boolean Local llOk, lcAlias llOk = .T. lcAlias = Alias() Select(m.lcAlias) Try Copy To (This.cDatei + This.cDateierweiterung) Type Csv Catch llOk = .F. EndTry Return m.llOk EndFunc EndDefine *--- Export nach XML Define Class ExportXml As AbstraktExport cDateierweiterung = \".xml\" cBeschriftung = \"XML-Datei (*.xml)\" Function Schreiben() As Boolean Local llOk, lcAlias llOk = .T. lcAlias = Alias() Select(m.lcAlias) Try CursorToXML(m.lcAlias, ; This.cDatei + This.cDateierweiterung, ; 1, 4+8+48+512, 0, \"1\") Catch llOk = .F. EndTry Return m.llOk EndFunc EndDefine *--- Export nach Excel (einfach) Define Class ExportExcel As AbstraktExport && (konkrete Strategie) cDateierweiterung = \".xls\" cBeschriftung = \"Export nach Excel\" Function Schreiben() As Boolean Local llOk, lcAlias llOk = .T. lcAlias = Alias() Select(m.lcAlias) Try Copy To (This.cDatei + This.cDateierweiterung) Type Xl5 Catch llOk = .F. EndTry Return m.llOk EndFunc EndDefine Insgesamt gewohnter Anblick und vertraute VFP-Befehle. Dennoch sehen wir, dass der Export nach Excel und CSV über Copy To erfolgt, während die Ausgabe nach XML über CursorToXML() realisiert wird. Und hier zeichen sich bereits die ersten Vorteile unserer Strategie ab. Egal, welches Ausgabeformat wir haben wollen, würden wir aktuell immer nur die Methode Schreiben() ausführen. Die Kapselung (oder der Kontext) Gut, zum gegenwärtigen Zeitpunkt haben wir nur ein paar lose Klassendefinitionen rumfliegen. Natürlich wollen wir in unserer Anwendung nur einen zentralen Punkt haben, über den wir den Export veranlassen können. Weiterhin wollen wir ebenfalls nicht, dass an beliebigen Stellen einfach mal zig Instanzen unserer konkreten Strategien produziert werden. Daher erstellen wir uns eine weitere Klassen, die zur Verwaltung unserer unterschiedlichen Ausgabeformate genutzt wird und als Kommunikationspartner mit unserer Anwendung zur Verfügung steht. *--- Verwaltungsklasse unserer Ausgabeformate Define Class ExportHandler As Custom && (Kontext) Protected oExportTyp oExportTyp = .Null. Function Init() As Boolean *--- Initialen Exporttyp laden (Default) This.oExportTyp = CreateObject(\"ExportXml\") EndFunc Function Destroy() As Boolean This.oExportTyp = .Null. EndFunc Function SetzeExportTyp(tcExportTyp As String) As Boolean This.oExportTyp = .Null. Try This.oExportTyp = CreateObject(\"Export\" + m.tcExportTyp) Catch This.oExportTyp = .Null. Finally Return (Vartype(This.oExportTyp) == \"O\") EndTry EndFunc Function GebeExportTyp() AS String Return This.oExportTyp.cBeschriftung EndFunc Function Schreiben(tcDateiname As String) As Boolean This.oExportTyp.cDatei = Juststem(tcDateiname) This.oExportTyp.cVerzeichnis = Evl(JustPath(tcDateiname),FullPath(\"\")) Return This.oExportTyp.Schreiben() EndFunc Function ToCsv(tcDateiname As String) As Boolean This.SetzeExportTyp(\"Csv\") Return This.Schreiben(m.tcDateiname) EndFunc Function ToXml(tcDateiname As String) As Boolean This.SetzeExportTyp(\"Xml\") Return This.Schreiben(m.tcDateiname) EndFunc EndDefine Ich habe hier zwei Ansätze in der Klasse implementiert: Allgemeiner Ansatz, wir haben die Kontrolle über das gewünschte Ausgabeformat Vereinfachte Methoden (ToCvs, ToXml, etc.) Und so langsam dürfte erkennbar sein, warum das Entwurfsmuster Strategie sehr nützlich ist. Wir müssen nun in unserer Anwendung nur noch den Kontext instanziieren und alles weitere geht dann automatisch. oExport = CreateObject(\"ExportHandler\") oExport.SetzeExportTyp(\"Excel\") ? oExport.Schreiben(\"C:TempMeinExportExcel\") ? oExport.ToXml(\"C:TempMeinExportXML\") Ausbaustufen Gut mit diesem Grundgerüst können wir bereits erste Ergebnisse produzieren und fleissig arbeiten. Sollten wir die Anforderung erhalten, weitere Ausgabeformate in unserer Anwendung anzubieten, dann ergeben sich uns hier maximal zwei Schritte, die wir durchführen müssen. Als erstes erstellen wir eine neue Klasse für das geforderte Ausgabeformat (notwendig) und als zweites können wir im Kontext eine neue Methode anbeiten (optional), die das neue Format nach außen anbietet. Und das war's bereits. Die Methode im Kontext zum Einstellen des gewünschten Exporttyps kann man übrigens noch problemlos aufbohren. Wir haben in unseren Anwendungen eine Verwaltungstabelle für verschiedene Import- und Exportformate. Dabei nutzen wir innerhalb der Anwendung, so wie hier im Code Tokens und setzen eine Abfrage auf die Tabelle ab, um die Informationen zu Klassenname, Klassenbibliothek, Beschriftung, Dateierweiterung, etc. in Erfahrung zu bringen. Das gezeigte Beispiel behandelt den Export von Informationen. Natürlich lässt sich das Konzept auch auf andere Szenarien anwenden, hier mal ein paar Möglichkeiten: Import g, Verschlüsselung, Konvertierung, und so weiter... Übrigens, wenn man den Druck ebenfalls als Datenexport betrachtet, braucht man lediglich eine neue Klasse zu schreiben und anzubinden. Was potentiell für einfache Reports sehr praktisch sein kann. *--- Export als Druckdokument Define Class ExportReport As AbstraktExport Protected cReportDatei cReportDatei = \"Schnellausgabe.frx\" Function Schreiben() As Boolean Local llOk, lcAlias llOk = .T. lcAlias = Alias() Select(m.lcAlias) Try Report Form (This.cReportDatei) To Printer Prompt Catch llOk = .F. EndTry Return m.llOk EndFunc EndDefine Okay, jetzt aber genug... Ich hoffe, dass dieser Artikel das grundsätzliche Konzept des Strategie-Muster ausreichend erklärt, und dass der gezeigte Quellcode leicht verständlich sowie nachvollziehbar ist. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'design-pattern-strategy.html'; this.page.identifier = '9271ed1c97_iddesign-pattern-strategy'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devcon---vorbereitung.html": {
    "href": "devcon---vorbereitung.html",
    "title": "Devcon - Vorbereitung - Get Blogged by JoKi",
    "summary": "Devcon - Vorbereitung Copy Markdown View Markdown GitHub Markdown Source Devcon - Vorbereitung Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon---vorbereitung'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon---vorbereitung'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Devcon - Vorbereitung') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-27 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Puuh, nach einem intensiven Wochenende Sessionunterlagen schreiben, freue mich nun voll und ganz auf die 13. Entwicklerkonferenz für Visual FoxPro am 9. bis 11. November in Frankfurt. Dies ist inzwischen meine achte Teilnahme an diesem jährlichen Ereignis in der deutschen FoxPro-Szene. Und seit 2002 bin ich jährlich als Redner zugegen gewesen. Ich bin wirklich sehr gespannt. Dieses Jahr werde ich ein wenig über das Zusammenspiel zwischen Visual FoxPro und dem .NET Framework 2.0 'plaudern'. Zunächst zeige ich die Funktionsweise von NET4COM - eine Komponente von Sedna - und dann geht's in das Abenteuer zur Erstellung eines Runtime Hosts für die Common Language Runtime (CLR). NET4COM ist kurz gesagt ein COM Wrapper auf eine Auswahl an .NET 2.0 Klassen und eignet sich selbstverständlich für jegliche COM-Clients; nicht nur VFP. Im Gegensatz dazu geht die Entwicklung des CLR Host genau in die andere Richtung. Hier versuchen wir .NET Code direkt innerhalb von Visual FoxPro anzusprechen und auszuführen. Der Dreh- und Angelpunkt für diesen Ansatz basiert auf der Tatsache, dass FoxPro seit jeher mittels C/C++-Code in Form von sogenannten FoxPro Link-Libraries nahezu beliebig erweitert werden kann. Ich möchte an dieser Stelle jedoch nicht allzu viel vorweg nehmen. Es würde mich freuen, euch alle dieses Jahr in Frankfurt begrüßen zu können und hoffe, dass ihr meine Sessions besucht. \uD83E\uDD2A Die Tage sind gezählt... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devcon---vorbereitung.html'; this.page.identifier = '9271ed1c97_iddevcon---vorbereitung'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devcon-ein-erster-eindruck.html": {
    "href": "devcon-ein-erster-eindruck.html",
    "title": "Devcon: Ein erster Eindruck - Get Blogged by JoKi",
    "summary": "Devcon: Ein erster Eindruck Copy Markdown View Markdown GitHub Markdown Source Devcon: Ein erster Eindruck Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-ein-erster-eindruck'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-ein-erster-eindruck'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Devcon: Ein erster Eindruck') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-11-09 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Die 11. Visual FoxPro Entwicklerkonferenz hat begonnen. Aktuell zwar nur der Teil für die Redner, aber es macht bereits jetzt schon Spass. Gerade das Treffen mit den Kollegen ist immer wieder erfreulich. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devcon-ein-erster-eindruck.html'; this.page.identifier = '9271ed1c97_iddevcon-ein-erster-eindruck'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devcon-prag-2005-termin-steht.html": {
    "href": "devcon-prag-2005-termin-steht.html",
    "title": "Devcon Prag 2005 - Termin steht - Get Blogged by JoKi",
    "summary": "Devcon Prag 2005 - Termin steht Copy Markdown View Markdown GitHub Markdown Source Devcon Prag 2005 - Termin steht Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-prag-2005-termin-steht'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-prag-2005-termin-steht'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Devcon Prag 2005 - Termin steht') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-03-29 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Kleines Update, denn die englischen Infos wurden aktualisiert und endlich habe ich den Dreh mit dem Datum raus... \uD83D\uDE01 Also, die Woche vom 20. bis 24. Juni ist reserviert, egal was da kommen mag... Außer ich würde tot umfallen \uD83D\uDE09 Wenn's auch interessiert: https://www.daquas.cz/fox/devcon2005an/ Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devcon-prag-2005-termin-steht.html'; this.page.identifier = '9271ed1c97_iddevcon-prag-2005-termin-steht'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devcon-prag-2006---day-1.html": {
    "href": "devcon-prag-2006---day-1.html",
    "title": "Devcon Prag 2006 - Day 1 - Get Blogged by JoKi",
    "summary": "Devcon Prag 2006 - Day 1 Copy Markdown View Markdown GitHub Markdown Source Devcon Prag 2006 - Day 1 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-prag-2006---day-1'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-prag-2006---day-1'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Devcon Prag 2006 - Day 1') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-11 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nach einer kurzen Nacht, etwa 5 Std oder so und einem kurzen Frühstück ging's sofort zur Registrierung für die heute startende VFP-Konferenz in Prag. Glücklicherweise lief bis jetzt alles sehr relaxed ab. Keynote A tribute to community and everything connects - ich denke, dass dürfte die beste Beschreibung der Keynote sein. Mit dem obligatorischen Gedenken an Drew und Brent Speedie startete YAG seine Premiere in Prag. Und das obwohl er bereits seit nunmehr 4 Jahren eingeladen wurde. Tja, gut Ding will Weil haben. Alan brachte im Laufe der nächsten zwei Stunden viele Details und Infos aus den Aktivitäten der Community - etwa die Videos von Kevin Ragsdale, die anstehenden Entwicklungen von Sedna, die CodePlex Entwicklungsplattform und eines der Projekte: VFPX (aka SednaX). Am interessantesten waren sicherlich die Erweiterungen und Möglichkeiten, welche durch das Service Pack 2 und Sedna uns VFP-Codern gegeben werden wird. Hierunter fallen weitere Verbesserungen für das Reporting System von VFP; etwa dynamische Eigenschaften und Rotation für Reportobjekte. Selbstverständlich mit den entsprechenden visuellen Buildern. Understanding Windows Communication & Presentation Foundations WCF (aka Indigo) ist die Summe aus System Messaging, WSE, Remote Messaging, ... Vereinheitliche Kommunikationsmöglichkeiten über mehrere Wege und Zusammenfassung der API der unterschiedlichen Technologien in eine einzige API. Service Oriented Architecture (SOA) Interop mit anderen Systemen wie etwa Java möglich Es gibt hierbei diverse Hosts wie etwa WAS, WPF, .EXE, NT Service oder COM+ Das Messaging erfolg über Channels; dabei handelt es sich sowohl um Protokolle, wie etwa TCP, aber auch um höhere Kommunikationsmöglichkeiten, etwa Security Channel. Interessanterweise können die Kommunikationswege ad hoc zur Laufzeit per XML-Konfigurationsdateien ausgetauscht werden. Weiterhin erfolgt die Unterhaltung über einen sogenannten Vertrag (Contract) zwischen Client und Server (genauer über die vorhandenen Endpoints), über den die Konditionen der Verbindung definiert sind und für den Verlauf der Unterhaltung Gültigkeit besitzen. ABC von WCF: Address (Where?), Binding (How?) und Contract (What?) Im Interface der .NET Klassendefinition wird durch Direktiven für WCF erweitert: ServiceContract oder OperationContract - Referenz zu System.ServiceModel nicht vergessen. Die Demo zeigte die Funktionalität sowohl in einer Konsoleanwendung wie auch gleichzeitig im IE. WCF ermöglicht es, auf einfache und universelle Art und Weise einen Applikationserver in .NET zu realisieren. WPF (aka Avalon) ist eine Modernisierung der etwas in die Jahre gekommenen GDI-Fähigkeiten kombiniert mit den modernen Darstellungsmöglichkeiten in GDI+, 2D, 3D, Video, und so weiter... Bei WPF handelt es sich um eine integrierte, vektorbasierte Darstellungsengine, welche intern auf DirectX zur Visualisierung zugreift und die Darstellung sauber von der Funktionlität trennt. Die Präsentation wird mittels XAML deklarativ formuliert und zur Anwendung gebracht. Hehehe, mit dem Ausblick auf die Interop-Kapazität von Sedna wird es extremst spannend werden, wenn man die beiden Technologien miteinander kombiniert (Two Secret Weapons combined). \uD83D\uDE0E XAML als deklarative Sprache für die Benutzeroberfläche ermöglicht es, die Aufgabenverteilung in derProjektentwicklung weiter zu separieren. Dadurch kann man wie bei der Webentwicklung verschiedene Teams zur Umsetzung beauftragen: Programmierteam und Visualteam. Ein wichtiges Kriterium, wenn man bedenkt, dass Entwickler nicht zwangsläufig Grafikdesigner und umgekehrt sind. XAML Plugin für weitere Browser wie etwa Safari, Mozilla oder Firefox sind seitens Microsoft angedacht. Die Verwendung von XAML in der Webumgebung wird primär mit dem IE7 möglich werden. Potentiell wird dieser Ansatz auch auf weitere Betriebssysteme erweitert werden. Damit kann man durch die Kombination eines Windowsbasierten Applikationsserver und diversen Clients sehr viel umfangreicher unterschiedliche Kunden ansprechen und für die eigenen Anwendungen gewinnen. XamlPad kann zum Testen von deklarativem XAML genutzt werden. Interessanterweise ist die Kombinationsfreiheit für das Zusammensetzen der UI extremst hoch. Als Beispiele wurden etwa Videos auf Buttons gezeigt, oder einen Button innerhalb einer Combobox. Ich denke, das hier eine tolle Spielwiese für coole Benutzeroberflächen zur Verfügung gestellt wird. Aber... es gilt zu beachten, dass man es nicht übertreibt, da die Benutzer auch intuitiv mit der Applikation in gewohnter Weise umgehen sollten. Extending VFP with VFP Seit Jahren ist Visual FoxPro als Produkt erweiterbar. Beginnend mit den _<Component> Systemvariablen in FoxPro DOS, über die Builder seit VFP 3 und der Einführung von IntelliSense in VFP 7.0. Durch die freie Verfügbarkeit des Quellcodes sämtlicher zusätzlicher Applikationen (XSource.zip) kann man nicht nur sehen, wie die Tools funktionieren, sondern hat die Freiheit diese zu ergänzen und in eigenen Anwendungen wieder zu verwenden. Dies ist alles unter What's New in den Solutions Samples eindrucksvoll hinterlegt, aber die meisten Entwickler wissen es nicht. Eine sehr gutes Beispiel ist die Funktionalität von _MenuHit. Hierüber kann man den nativen Code von VFP abfangen und eigene Tools integrieren. Etwa einen verbesserten Dialog für das Anlagen von Properties oder Methods. Die Technik dahinter wird wie so vieles über die Fähigkeiten von IntelliSense abgedeckt. Durch Anlage von neuen Datensätzen in der _FoxCode-Tabelle ist es möglich sämtliche Menüeinträge abzufangen und eigene Routine stattdessen auszuführen. Eine weitere Möglichkeit zum Verfeinern der IDE ist die Integration von eigenen PropertyEditoren in das Property Sheet. So, kann man für native und eigene Properties wiederum mehr Kontrolle produzieren. Etwa, dass man logischen Eigenschaften eine Combobox zur Auswahl des Wertes verabreichen kann. Bei der Entwicklung eigener Erweiterungen sollte man auf alle Fälle folgendes Statement bei der Entwicklung bzw. beim Debuggen absetzen: Sys(2030,1) Bedingt durch die Einschränkungen für die Zeichenlänge von VFP-Eigenschaften kann man die Verweise auf die PropertyEditor ebenfalls in der _FoxCode ablegen. Man muss dazu im Memberdata Editor lediglich 'Global' verwenden. Durch Verwendung einer tabellenbasierten Factory mit gleichartigen Editorklassen erzeugt man sich sehr viel Flexibilität für die tägliche Programmierung. Durch Abfrage auf die Tabelle erhält man eine Objektreferenz auf den gewünschten PropertyEditor und kann dann die spezifische Bearbeitung für das gewählte Attribut umsetzen. Mittels IntelliSense können übrigens auch technische Richtlinien bei der Programmierung eingehalten werden. Etwa die implizite Erweiterung des Local-Statement für Variablen. Doug Hennig zeigte dies sehr eindrucksvoll mit Hilfe eines eigenen Skripts. Im übrigen gibt es im FoxWiki sehr nützliche Beispiele für Custom IntelliSense Scripts. Mit dem Ausblick auf den My-Namescape waren hier viele Informationen in kurzer Zeit zum Thema Erweiterbarkeit von Visual FoxPro verfügbar. Object Thinking Die Historie der verschiedenen VFP-Versionen als Aufhänger zur Session. Wie hat sich die Denkweise der eigenen Programmierung im Laufe der Zeit in den verschiedenen Versionen geändert? gab es überhaupt eine persönliche Weiterentwicklung, oder doch nur eine Aktualisierung des Werkzeugs bei gleicher Arbeitweise. Literatur: MS Press Object Thinking by David West. Everything is an object Simulation of the problem aids in object discovery and definition (Modelling) Objects must be composable (Building larger objects) Distributed objects must replace hierarchical centralized control (Independence) Was macht ein Objekt eigenlich aus? Inheritance, Responsibility (One thing and one thing only), Delegation (Aufgaben abgeben), Polymorphism (Gleicher Name bei unterschiedlichem Verhalten) und Encapsulation (Autarkes Arbeiten mit Wissen über weitere Informationsbeschaffung; Bsp: Telefonnummer von jemand herausbekommen) Wie können wir die Ziele erreichen? Gemeinsames Vokabular und Grammatik definieren, etwa durch Erstellung eines Glossar Evaluation criteria - Zielvorgabe definieren, denn der Kunde weiß nicht, was eigentlich er will Decomposition based on \"natural joints\" in the domain - Zerlegung der natürlichen Verhältnisse in kleinere Bausteine. Responsibility assignment based on behavior - jedes Objekt hat nur eine einzige Aufgabe und ist dafür verantwortlich. Aggregation of objects by interaction A way to add design und implementation detail - Flexibilität für spätere Anpassungen und Erweiterungen berücksichtigen Adding IntelliSense to Your Applications Scripting, Expression Builder oder AutoCorrect (Commonly Expressions und Abbrevs) mit Auswahllisten. Die Systemvariablen _CodeSense und _FoxCode sind in der Runtime leer, sobald diese gesetzt werden, funktioniert auch IntelliSense zur Runtime. Am einfachsten erzeugt man sich einen generischen Stub für alle Projekte. _CodeSense = \"foxcode.exe\" _FoxCode = \"foxcode.dbf\" Dazu erstellt man sich ein neues Projekt und ergänzt die beiden Dateien foxcode.prg and foxcode2.dbf. Das war's auch schon fast. Ein wenig die überflüssigen Bestandteile entsorgt und kompilieren. Durch Löschen von vorhandenen Datensätzen kann man dem Anwender einen reduzierten Satz der Befehle geben. Klingt soweit schon sehr motivierend, aber leider gibt's ein paar Restriktionen: IntelliSense funktioniert nur in PRG und Memos (hier nur unter bestimmten Bedingungen) und nicht wie vllt. gedacht in Editboxen. Die beste Empfehlung ist der Weg über PRGs: Modify Command (m.lcFile) Window (m.loForm.Name) Bei einer Top-Level Form muss die Klausel 'In Window' angegeben werden. Weiterhin ist es sicherlich sinnvoll, wenn man in der Anwendung eine Möglichkeit zur Verwaltung der Snipplets anbietet. Bei Klassen ist es hilfreich, wenn man ebenfalls eine Möglichkeit anbietet, um überflüssige PEMs ausblenden zu können bzw. nur die benötigten anzeigt. Stichwort: Favoriten (FfiBuilder) Abschliessender Eindruck des ersten Tages if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devcon-prag-2006---day-1.html'; this.page.identifier = '9271ed1c97_iddevcon-prag-2006---day-1'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devcon-prag-2006---day-2.html": {
    "href": "devcon-prag-2006---day-2.html",
    "title": "Devcon Prag 2006 - Day 2 - Get Blogged by JoKi",
    "summary": "Devcon Prag 2006 - Day 2 Copy Markdown View Markdown GitHub Markdown Source Devcon Prag 2006 - Day 2 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-prag-2006---day-2'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devcon-prag-2006---day-2'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Devcon Prag 2006 - Day 2') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-12 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Windows Component Services Alle Jahre wieder vergibt Microsoft neue Namen für vorhandene Technologien. Okay, es ist weitaus mehr als nur eine schlichte Namensänderung. Schliesslich wird damit auch angezeigt, dass Neuerungen realisiert wurden. SetComplete() beendet die Transaktion und entsorgt das COM+ Objekt im Speicher. Für Windows Component Services gibt es zwei Ebenen der Sicherheit - deklarativ und programmatisch. Die deklarative Sicherheit wird im MMC-SnapIn realisiert und verwendet die vorhandenen Sicherheitsmöglichkeiten von Windows. Beim Erstellen von Rollen für eine Komponente empfiehlt es sich, dass man eine Benutzergruppe statt einzelner Benutzer hinzufügt. Auf diese Weise kann man sehr einfach den Zugriff von außen auf die Komponente konfigurieren. Sinnvollerweise gehen diese Einstellungen bis auf Methodenebene. Somit kann man bspw. allen Gruppen die Initialisierung gestatten, aber einzelnen Gruppen den Zugriff auf bestimmte Methoden verweigern. Zum Abfangen des COM-Fehlers, etwa bei \"Zugriff verweigert\", kapselt man den Aufruf in einen Try..EndTry-Block und kann stressfrei die lokale Anwendung laufen lassen. LINQ Nach der anstrengenden Runde zu Distributed Applications mit COM+ und den diversen VFP Projekten samt Komponentenprogrammierung, COM+ Softwareaddons des Betriebssystems und so weiter, kommt eine kleine Entspannungsrunde mit Zukunftsaussichten von Microsoft ganz recht. Der Begriff Language INtegrated Query (LINQ) macht ja bereits seit einiger Zeit verstärkt Werbung für das kommenden .NET Framework 3.0, und die gezeigten Beispiele und Einsatzmöglichkeiten von Alan lassen auch auf coole Möglichkeiten spekulieren. Aber ganz ehrlich gibt's hier zwei Wehrmutstropfen: Es ist einiges an VFP-Knowledge in LINQ eingeflossen und die bisherigen Beispiele zeigten immer nur Abfragen (oder hat jemand schon ein Update oder Delete-Statement bzw. Aufruf einer Stored Procedure in LINQ gesehen?). Okay, es ist noch im Alpha-Status und warten wir mal ab, was noch kommen wird... dennoch ein durchwachsener Eindruck. XFRX - https://www.eqeus.com/ Als German distributor für XFRX schaue ich mir selbstverständlich die Session von Martin Haluza auf Englisch. Zum einen möchte ich mir die Neuerungen der Version 12.0 ansehen und zum anderen direkten Kontakt mit ihm pflegen, und ein wenig Smalltalk halten. Neben der Tatsache, dass ProLib Tools XFRX vertreibt und supportet, habe ich die deutsche Übersetzung der UI durchgeführt und schreibe ein wenig an einem Plugin für die Active FoxPro Pages. Was mich immer wieder fasziniert, ist die Möglichkeit, dass man in XFRX einen Report mitsamt der Daten als Datei im XFF-Format speichern und später stressfrei wiederverwenden und in die angebotenen Dokumentformate exportieren kann. In der Version 12.0 wurde verstärkt der Preview verbessert. So sind bspw. mehrere Seiten nebeneinander anzeigbar und Reports können kaskadiert werden, etwa zur Darstellung von 1:n-Relationen. Auch Hyperlinks in HTML und PDF sind direkt nutzbar. [b]Cool Uses for ReportListeners** Hallo erstmal. Ich weiß ja nicht, ob sie es wussten... ... aber Visual FoxPro 9.0 hat eine komplett überarbeitete ReportEngine. Und was man damit anstellen kann, ist schlichtweg oberhammermäßig cool. Der komplette Report mutiert de facto zu einem 'Container-Control' mit dem man beliebige Ergänzungen und VFP-Code nutzen kann. Neben den vielen Exportoptionen bietet die Klasse ReportListener nahezu unbeschränkte Möglichkeiten für die Verarbeitung von VFP-Reports - The sky is the limit! Ich warte schon extremst gespannt auf die Sessionunterlagen von Doug Henning, um intensiver mit den Beispielen zu arbeiten. Achja, auf der Website VFP Report Listener Headquarters gibt's übrigens frei verfügbar nützliche Klassen für die Druckaufbereitung. Schaut euch auf alle Fälle mal die NavPane-Klasse an. Ziemlich cool und total easy in der Implementierung... Q&A und Fazit zur Konferenz Jede Konferenz nimmt leider ihr Ende und ich sehe der ganzen Sache mit einem weinenden und lachenden Auge hinterher: 2006 in Prag geht zu Ende, aber 2007 wird wieder stattfinden. Und ich werde wieder da sein. Es ist wirklich schade, dass man oftmals nur im Rahmen einer Konferenz zum Treffpunkt mit VFP-Entwicklern aus anderen Nationen nutzen kann. Nun, schauen wir mal... Es hat mir persönlich auf alle Fälle wieder sehr viel Freude und Spass gebracht. Die nächsten Termine sind schon in der Planung bzw. Sichtweite: dFPUG VFP Entwicklerkonferenz 2006 in Frankfurt AtoutFox 2006 in Paris (noch offen) Devcon Moskau 2007 (noch offen) - hier hoffe ich auf den FoxClub.ru Bis dahin wird sicherlich noch sehr viel in der Fuchsszene passieren. Euer Harry Hirsch aka JoKi... \uD83D\uDE09 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devcon-prag-2006---day-2.html'; this.page.identifier = '9271ed1c97_iddevcon-prag-2006---day-2'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "developer-creators-and-online-communities-summit.html": {
    "href": "developer-creators-and-online-communities-summit.html",
    "title": "Developer Creators and Online Communities Summit - Get Blogged by JoKi",
    "summary": "Developer Creators and Online Communities Summit Copy Markdown View Markdown GitHub Markdown Source Developer Creators and Online Communities Summit Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'developer-creators-and-online-communities-summit'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'developer-creators-and-online-communities-summit'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Developer Creators and Online Communities Summit') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-02-01 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); New year, new activities. It's still January and I'm already on tour. I was heading to the Developer Creators and Online Communities Summit - #GoogleDCOC - in Singapore. It's a global hybrid event - meaning in-person and online. The purpose is to bring together content creators and online community managers in the developer space from all around the world. Why attending in-person? I'm excited about this summit for various reasons. First and foremost, apart from blogging since over a decade I'm diving more and more into other ways of content creation. Already back in 2020, we recorded the whole Virtual Developers Conference in Mauritius - 4 tracks over 3 days, more than 100 hours of technical presentations - then post-processed everything and re-published every single session on its own. Everything available on YouTube. That conference alone has over 90 videos in its playlist. Additionally, I started recording talks happening during our monthly MSCC meetings. Most of them still need processing but are coming to the YouTube channel soon. Pinky promise! And not to forget: Podcasts!‌‌ Although the concept and \"launch\" of the Talks on Software Craftsmanship has been around since 2020 as announced at the end of the Virtual Developers Conference it didn't take off yet. Unfortunately, for various reasons which are mainly down to the procrastination of the host(s). Which is another pinky promise: Reviving, eh really launching, this podcast. And what's a good motivator for that? Yes, you're right, podcasting. So, recently my friend and long-time community supporter Loic Forget took the lead in realising the podcast \"Mau Pas Konpran!?\" (published on Open Spotify). Together with Ish Sookun we are sitting together for a semi-technical, monthly recording session. The fun part here is that each episode is held in typical Mauritian style. We are conversing about local topics in at least three languages - Kreol Morisien, English, and a little bit of French or German. For me, that's a great learning experience on multiple levels. Attending the summit I was highly interested in networking with other creators and to learn from their experiences. Which equipment to consider and which software applications are recommended for processing. Tips & tricks publishing on YouTube and how to improve the experience for viewers. Second reason is the destination, Singapore. It's going to be my second trip to Asia after speaking at the C# Corner Conference 2017 in India. I have watched a number of documentaries about the city country during the past years, and the broadcast of the Formula 1 race is one of the highlights in my \"sports calendar\". Combining my attendance at the DCOC summit with the ability to explore the city of Singapore during two spare days, Wednesday and Saturday, sounded like an opportunity not to miss. But hang on... JoKi, I thought you'd be a software crafter. How does this relate to your in-person attendance?‌‌In my case this might be the answer: \"Online community managers: manage online tech communities on Discord, Telegram, Slack or other platforms (1000+ members/subscribers/followers).\" - I founded and I'm still co-managing the two largest communities in Mauritius, namely Mauritius Software Craftsmanship Community (MSCC) and GDG Mauritius with a combined total count of over 4,000 members. With social media presence on X/Twitter, Facebook, and LinkedIn reaching almost 9,000 people. Even our YouTube channel has close to 400 subscribers. So, why attending in-person? The direct interaction with other peers is priceless. Getting into a conversation with other attendees is so much easier than in online sessions (with chats). The learning factor from others' experience is magnitudes higher, more relatable and up close personal. Similar to our regular user group meetings. Travelling to and fro, and how Similar to previous trips I decided to travel light again, despite being out of the country for the six days. A small carry-on trolley for a bare minimum of clothes topped up with my daily backpack containing all kind of equipment. The focus was on everything using a single connector, USB-C. Hence I assembled the following equipment to bring along. Lenovo Chromebook (USB-C charging) Google Pixel 6a (USB-C charging) Google Buds Pro (ANC and USB-C charging) Valco VMK20 (ANC and USB-C charging) 10,000 mAh battery power pack (USB-C charging) Hama 60W multi-plug USB-C charger (4 outlets) Cables. Lots of cables. USB cables, an HDMI cable, and a network cable. Let's see whether I get to hack that SmartTV at the hotel. \uD83E\uDD23 Regular readers might have had a good idea already. At a certain stage I was even considering to leave any laptop at home. But then, why would you go to a Google Summit without a laptop, especially without a Chromebook? For one of the workshops it was communicated upfront that you have to have a laptop with you. So there we go. Unfortunately, I didn't pack my lavalier microphone which would have been very handy for some ad hoc on the spot recordings / live broadcast from the summit or from Singapore. The choice of airline was easier for this trip. Although there are direct flights from Mauritius to Singapore, operated by Air Mauritius, there are a limited number of flights per week only. Such a flight would take roughly eight hours but the available options didn't fit well with the agenda scheduled. In consequence, I looked at the multiple flights per day on Emirates. and booked the round-trip taking into account that it took twice the time including a short stop-over in Dubai. Surprisingly, the price difference between those two choices is marginal. The flexibility advantage is impressive and actually I'm fewer days out of office than otherwise. Who'd expected that? Speaking of expectations, Singapore requires all foreign travellers to submit the SG Arrival Card (SGAC) before arriving. There are two options to complete that requirement. Either you use their SGAC e-Service online or you install their MyICA Mobile application. I went for the later and it was done after a few clicks here and there, and uploading a scan of my passport. The SGAC is equivalent to the All-in-One Health Form when someone travels to Mauritius. Touchdown Singapore After the welcoming stop-over at the Emirates Business Lounge at the Dubai airport I arrived a little bit ahead of time in Singapore. And wow, what a difference the airport is. Changi Airport and public transport I've been to a few airports around the world so far and Changi is by far the most impressive one. Think of an airport inside a huge shopping mall and entertainment facility and you might get an idea about it. This thing is huge! Did I mention that free WiFi is offered everywhere? Next task was to purchase one of those NFC cards for public transport. To my understanding there are at least two possibilities - ezLink and SimpleGo. I spent like $S 10 for the ezLink card which came pre-loaded with a balance of $S 5 to ride the MRT lines and busses. As for mobile data it was even easier. Ahead of my trip I purchased a SingTel Tourist eSIM for their 5G network online. Then you install their hi!App and you can register/activate the eSIM card in your mobile after you have connection with their network. A few identification steps and confirmation emails later I was able to use their 5G data network instantly. Checkout the speed rates. OMG ! That's a quick test while riding the Skytrain between terminals at Changi airport, Singapore. 500+M ps! \uD83D\uDE33\uD83D\uDE33\uD83D\uDE33https://t.co/UF1nsgByAX — Jochen Kirstätter (JoKi) (@JKirstaetter) January 23, 2024 Ludicrous speed rates on the SingTel 5G network - Impressive! Another positive surprise was the automated immigration and passport stations. Armored with your passport, the SGAC code received and yourself you have everything to get through. For me, it was as smooth as the automated passport stations at German airports. Maybe even smoother... Passport/security check out at the airport... Whizz, done. Scan passport, scan digital SGAC, scan finger, ✅ Less than 10 minutes. The queuing took most of the time. 2 people ahead of me.#GoogleDCOC #GoogleDevCommunity — Jochen Kirstätter (JoKi) (@JKirstaetter) January 23, 2024 Blazing through the passport & immigration check at Changi airport in Singapore Next, I went straight to the MRT station at Changi airport to catch a ride into the city. To get around I installed the MyTransport.SG mobile application which provides the network layout of the different MRT lines and the bus lines with all stations. Alternatively, Google Maps works just fine, too. Actually, I downloaded the map of Singapore for offline use. You know, just in case I might get lost without data network, etc. Chinatown and staying at a capsule hotel I boarded the MRT green line shortly after 22.00 hrs, and had to change to the Downtown (blue) line at Expo station before arriving at the DT 19 - Chinatown - station. Despite the late hour businesses and restaurants are still buzzing over there. Glad I chose this suburb to stay the first night. At least, I knew there would be various options to have a tasty dinner, and opportunities to shop anything I might have forgotten to bring along. The capsule hotel I booked was within 5 minutes walking distance from the station exit Pagoda Street, and following the directions on Google Maps got me there easily. A quick scan of my passport at their automated check-in kiosk checked me in, and I received the key card in exchange. Again, really smooth and user-friendly processing. Here are a few impressions. With the key card you get access to the different levels and amenities of the hotel. I reached my sleeping capsule, dropped of the luggage in the designated, key card secured locker storage, and then went on tour in Chinatown for dinner. I wasn't sure how long restaurants would be open but thought that probably midnight might be a closing time anywhere. In short distance across the hotel I found a place with yummy-looking dishes and I decided to give it shot. I mean, what could possible go wrong? BTW, the ratings on Google Maps for that joint are positive and their range of dishes offered is wide. Around 1:30 hrs in the morning I finally went to sleep in my capsule. Well fed, cleaned up after having had a shower in the common washroom area of the hotel, and tired from the long trip. Day 1 Exploration time! First day in Singapore had no fixed agenda and it gave me the opportunity to explore the city freely. After a few hours of rest and the generous offer by the capsule hotel to store my luggage beyond the checkout I asked them for a few must-visit places in town. Exploring Chinatown As I spent the night in Chinatown what's more obvious than to discover the suburban during the morning hours? A quick check on Google Maps that the famous Buddha Tooth Relic Temple is literally just around the corner. By the time I got there prayers were still in full swing and it has an enlightening experience to stay for a while Down that same road I went to visit the Sri Mariamman Temple and there also had been prayers. Taking into consideration that Thaipoosam Cavadee was on Thursday I assumed devotees would be at the temple for prayers. Although I went back through Pagoda Street towards the colonial street shops I only saw the Jamae (Chulia) Mosque from a short distance but didn't go there. I was reminded of Mauritius with those three places in close, peaceful coexistence. The Bay area Closing the loop through Chinatown I went back to the TE 18 - Maxwell - station of the MRT and headed to the line's final destination in the south east: TE 22 - Gardens by the Bay. The weather was holding and so I first went to the Marina Barrage to get a view of the barrage bridge and dam, and the many ships out on the sea. The barrage building itself is super interesting as there are several slopes that allow you to walk on the top of it. Despite the light rain, yeah I brought a lightweight rain jacket with me, I then ventured to the Gardens by the Bay. Coming from the south I first visited the Kingfisher Lake with its small waterfalls and statues. Passing the Water Lily Pond and Sun Pavilion I headed straight to the Floral Clock. From there I got tickets for the Cloud Forest and the Flower Dome. The Cloud Forest with its indoor waterfall and six levels of nature exploration is impressive and amazing to visit. There are different sections of floral diversity which the Flower Dome has to offer. I enjoyed the smells, the tranquility in certain areas, and the massive educational factor across the dome. One could easily spend the whole day there. As lunch time started to close in, I went along the Dragonfly Lake towards the Bayfront Ave and ended up in The Shoppes at Marina Sands Bay. That's like shopping heaven on Earth \uD83D\uDE03 To wrap up the tour I took the Downtown line from DT 16 - Bayfront - back to Chinatown. Stopped by at the capsule hotel to charge my mobile a bit, chatted with the family back home in Mauritius, and then grabbed my luggage to move to the booked hotel by Google. This time I caught a bus and got off the station right in front of IKEA Alexandra. Our hotel - Momentus Hotel Alexandra - is next to it. After all the walking and discoveries during the morning and midday hours I was a bit exhausted and therefore relieved to stretch out on the bed to rest. The little time left until the official Welcome Reception next to the pool I used to review the tons of pictures I had taken, chatted a bit more with my wife and the kids, and had a refreshing shower. Welcome reception Our organising team from Google invited us to an informal welcome/reception dinner at the Momentus Hotel to gently kick off the summit. After a brief exchange with Dirk Primbs - asking him about getting accustomed in Canada - I took an empty chair at one of the tables and joined a group of developer creators from Hanoi, Vietnam: Huy Dang, Dang Dong, and Nguyen Hai-Truong. It was interesting to learn more about their activities and how they organise events. Thanks for the pin and stickers! Shortly after I spotted Yesaya Athuman who I missed talking to at DevFest Dar es Salaam. I went over to him joining others standing and chatting about all kind of community stuff. There I got introduced to Dariusz Kalbarczyk, organiser of ngPolandConf, and we exchanged a bit about our common friend Dhananjay Kumar, organiser of ngIndia. By chance we were also joined by Mazlum Tosun whose YouTube videos fascinate me as he creates Google Cloud content in French and English language, and promotes them equally. We instantly spoke about common friends in the community like Ezekias Bokove, Louis Japheth Kouassi, and Guillaume Blaquiere. While having dinner fellow creator and book author Muhammed Ahsan Ayaz spent some time and interesting stories with us. With the evening progressing and having had dinner from the buffet you could observe the forming of multiple groups based on either geographical vicinity or established connections on Discord. It always amazes me how easy it is to connect with other during such in-person gatherings. Together with Yesaya, I then sat down with Daisy Auma and Ezinne Osuamadi to learn more about their professional whereabouts and community involvements. It was actually fascinating to learn that Daisy moved from Kenya to the UK, whereas Ezinne is from Nigeria and working in Germany since a few years. The world is such a small village after all. During the evening I got to meet other participants from Turkmenistan, Kazakhstan, Uzbekistan, Mongolia, France, Italy, India, Bangladesh, Indonesia, and many more countries. Each single one with fascinating stories of content creation and community engagements. Very inspiring actually! Day 2 Finally the day has arrived! The summit is happening officially. A shuttle bus took us the short distance from the hotel to the Mapletree Business City where Google Singapore has its office. DCOC Summit An empty stomach isn't capable to study. Hence breakfast buffet had been offered by Google. A number of conversations from last night were reignited and new conversations came to life. Big Hello's and cheers everywhere around; this summit was so inclusive and welcoming. Our two Masters of Ceremony Alicja Heisig-Chiarello and Dirk Primbs launched the Summit officially and also welcomed everyone on the live stream. Meanwhile our two radio / live streaming moderators Nathalie Pereira and Alfredo Morresi had started already. Oh yes, amazingly the whole summit was a hybrid event with in-person and online participation. BTW, here's the agenda: https://rsvp.withgoogle.com/events/developer-creators-and-online-communities-summit/agenda Next up Janise Tan welcomed us to Singapore and the SEA developer community landscape giving us some details about the region, its numbers regarding to communities and activities. Fun fact, while introducing Singapore to the audience she highlighted some of the key factors of the city's success which miraculously match the secret ingredients being a successful content creator online or running thriving communities. True to the nature of a hybrid summit, we then watched the Creator keynote as a remote session with Stephanie Wong. After the Google Cloud Tech X/Twitter Space in April last year about How to make the most of Innovators Plus subscription I was eager to meet Stephanie IRL. Hmm, didn't happen this time... Her talk gave some good insights based on her long-year experience as a successful content creator. Building and telling a concise story is one of those golden aspects to take into consideration. Got an idea how Home Assistant started? Well, I didn't know until watching the Online communities keynote by Paulus Schoutsen who started one of the largest open-source projects on GitHub based on the basic need to / problem of improving home automation. The main takeaway for me was to create and nourish a culture of inclusive, friendly, and safe online community with gender-neutral terms. The following panel shared more tips on being successful creating content as well as entertaining a healthy community. Very helpful information has been shared by Stephanie and Paulus. After the well-deserved morning break that allowed us to reflect and network we got an Intro to Gemini, presented by Dirk Primbs. During the past decade there had been major breakthroughs in the field of Artificial Intelligence (AI). And although these days most people might talk about AI in the context of conversational bots or generative content creation there have been more highlights, i.e. in research & development sector, in the medical sector, and others. One should also understand that Machine Learning (ML) and Artificial Intelligence go way further back into the past than a decade but it's the magnitude of advancements that has happened recently only thanks to broader access to information and more capable computing power thanks to hyper-scaling data centres. Breakout sessions I attended Choosing a session was tough because all breakouts covered interesting topics. Here are the ones I found most attractive: Tips on creating a winning content strategy for your YouTube channel Ever wondered how YouTube handles recommendations? Tra Nguyen gave us a few insights and tips: ✅ Know your audience's needs and adapt accordingly ✅ Focus on your audience and the algorithm will follow ✅ Keep experimenting! The Simple Superpowers of Mozilla Hubs Part 1: Unleashing Innovation Through Community I was intrigued about a Mozilla session at a Google event, and had never heard about Hubs before. It's a private, virtual 3D world with endless possibilities of collaboration, according to Michael Morran. I'm wondering whether this could be a feature for the Developers Conference 2024 in Mauritius. Mastering Personalized Bot Build: A Discord App-building Workshop With just a few lines of code you can create your own bot(s) for Discord. Dirk Primbs explained to us that bots are literally treated as a Discord user account with permissions granted to react to certain events. The actual functionality of such a bot is down to your imagination. Podcasting on YouTube Following the sunsetting of Google Podcasts I wanted to know what's next. Thankfully Russell Yap had the right content for my curiousity. However with their move to YouTube Podcasts and YouTube Music I'm not thrilled at all, given that both services are not available in Mauritius. Plus, it requires Premium subscription to enable background play. Whew, people, I was exhausted after that whole day of high-value information. Lots of new impulses and aspects to reflect on. The complete (raw) recording is accessible here: Radio studio pre-show. Most likely post-processing might is going to chop the stream into individual sessions (soon). Lastly, our wonderful masters of ceremony Alicja and Dirk wrapped up the day with their closing remarks, informing us about the obligatory group photo, and how we are going to spent the evening together having dinner. Networking dinner Remaining in the same office building we went upstairs to the 30th floor. There you have an amazing over Singapore. Can't remember how many panorama shots I took but I must have been in each single corner of the building. Over dinner more networking and conversations happened. This time I buddied with fellow creators Alex Patterson who runs CodingCat.dev and Jai Campbell who's a Cloud Architect from the UK. Most interestingly we talked mainly about Singapore, discontinued food products and healthy nutrition, and about a variety of documentaries broadcasted on Netflix. IKEA Back at the hotel I \"had to go\" to IKEA, of course. Not because of me but rather due to the shopping list of my beloved wife. Given the dimensions of my carry-on luggage I could purchase a few smaller, lighter items only. The most important, pressing ones so to speak. More Networking Back at the hotel I didn't even bother to go upstairs to the room but stayed right away down in the lobby joining Karen Hsieh, Liang-Bin Hsueh, and a local friend of his. We kind of picked up the conversation(s) from earlier today. It was interesting to learn more about Taiwan, Mandarin language, and Chinese/Asian culture. Likewise they were open to learn more about Mauritius. Meanwhile Muhammed Ahsan Ayaz reached out to me asking where I would be around. I suggested to him to join us down in the hotel lobby. Already last week Ahsan posted in our DCOC summit channel on Discord and kindly offered physical copies of his updated, second edition book Angular Cookbook. I'm really happy about this addition to our community's book library. Funnily, he was a bit surprised when I asked him to have the book signed by him. \"That's the first time someone is asking for a signature!\" - I'm grateful for this. How often do you have a chance to meet a book author in person? Another summit fellow, Robert Theo from Indonesia, also got a signed copy and he then joined our little group for more networking fun. From there on we spoke a lot about Indonesia, Taiwan, Mauritius, food and cultural aspects, and so forth. Time was flying. Day 3 That night I managed to catch a few more Zzzz's, and like the previous morning we took the provided shuttle to get to Google Singapore. Rinse and repeat, one might think. Absolutely not, today was community day! DCOC Summit Following yesterday's sessions mainly provided by Googlers and partners, the content for today was sourced from the community itself. A total of nine creators offered their knowledge and expertise. Plus, the various time slots in the podcast studio. Our fantastic masters of ceremony - Alicja and Nathalie - launched the day with a brief overview of what to expect and introduced the speakers. Each presenter had an opportunity to pitch their content in front of the audience. Another tough decision process to choose three out of nine. Breakout sessions I attended Making a Mark on YouTube and Google: My Story First-hand shared experience always attracts my interest, and so I went to Abu Anwar MD Abdullah talk on the origins of The Flutter Way. It's kind of amazing how he started and is now entertaining the second most followed YouTube channel in the Flutter space globally. Podcast 101 - Dos and Don'ts Starting a podcast is easy and success follows instantly? Nope, think again. Being a co-host on the Mau Pas Konpran!? podcast and having a dangling Talks on Software Craftsmanship show I needed to know more. Thankfully Stefano Le Pera shared all the up's and down's of the ChatGDG podcast. A lot of what he mentioned resonated with my own experience. Time to get recording again... Generative AI for Visual Content Creation Are you concerned that AI might render your job position redundant? How about leveraging Generative AI to improve your skills and be more successful? Margaret Maynard-Reid, a ML Engineer / 3D Fashion Designer, gave us an overview of available types of Generative AI, and how she's benefiting from them in her daily work. Very useful content for my own needs. During lunch break I seized the opportunity to hang out with numerous participants. Among those are Sathish VJ, Madona Wambua, Neïl Rahmouni, Cyrus Wong Chun Yin and Kuan Hoong Poowhom I follow on social media since a while already. Another fellow creator from the Sub-Saharan Africa region, Amani Bisimwa, had arrived at the summit. It was a great time to network again. And like the previous day, the recording is available here: Welcome to Day 3 and introduction to community-driven sessions Gardens by the Bay and Spectra - At night! I knew about the light show at the Gardens by the Bay and I had planned to go there tonight. So, it was a delight for me that Arianna Capizzi of ChatGDG announced that there would be a group of people heading to the same destination. Awesome let's do it! If I recall correctly, we were roughly twelve to thirteen people - the Dirty Dozen figuratively speaking - that met down at the lobby of the hotel and hauled a few Grab drivers to get to the Gardens. It was definitely quicker and more comfortable than using public transport and while sharing the cost also not so expensive after all. With a selection of songs from various world-known musicals we enjoyed the light spectacle right in front of the Supertree Observatory and under the Skywalk. It was enchanting to say at least, highly recommended. Meanwhile I animated Lisa Carpenter to re-activate her account on Ingress and join me playing. We gladly explained to Monika Janota the purpose and the mechanics of the game. There is a crazy amount of portals all over Singapore. As the hunger kicked in slowly but surely we decided to head over to the hawker centre in the Marina Sands Bay. Taking the Marina Bay Overpass we crossed the Dragonfly Lake and went straight into The Shoppes at Marina Sands Bay. Down the escalators and then the long straight to the food court. Finding enough seats for such a big group at this time seems very challenging. And so unfortunately we had to split into smaller units, chope'd our places and went off exploring the different stalls sourcing for food. There is an incredible variety of dishes available. After we concluded our culinary adventures we headed to the Waterfront Promenade and attended the Spectra - A Light & Water Show. What a joy for eyes and ears. Later on we strolled down the Waterfront Promenade fully indulged in conversations of all kind of sorts, and marvelled together at the night skyline of Singapore. With all the talking we needed some drinks and therefore stopped by one the pubs in the surrounding. Before returning back to the hotel, we managed to visit Lau Pa Sat, the \"old market\" and first hawker centre in Singapore. What a remarkable day full of sharing community experience, learning, exchanging, networking, conversing, dining, and fun it was. Day 4 Exploration time, again! As my flight back home was scheduled for the evening, I decided to spend my last day in Singapore (for now) in Chinatown, again. Like previously, I checked out of the hotel first thing in the morning but also asked the concierge desk to store my luggage until the afternoon. Shop until you drop: Chinese New Year (CNY) Starting at the People's Park Complex mall I explored the street market in Chinatown, went to the Chinatown Complex, then back to Pagoda Street where I found a nice place to have lunch. As I was looking for something specific I went across the overpass back into the People's Park Centre discovering all kind of items at the Night Bazaar. And after checking out all levels inside Chinatown Point mall I was finally done. Unsurprisingly I had to buy another backpack (50l volume) to fit in all the items purchased. Given this half-day shopping spree I am not so sure about organising a (one week) vacation with the family here in Singapore is such a good idea. Especially around the time of Chinese New Year. Just kidding! See you soon! Reflecting over those past few days in Singapore while writing this blog recalled numerous conversations I had with other participants. I was excited meeting my community friends from the SSA region, finally being able to associate a face and person to the countless peers from the Google Developer Community Discord server, and massively enjoying the amount of new contacts made. Hooray to LinkedIn's QR Code feature which smoothlessly allowed all us to foster that new acquaintance on social media. I don't know about others, but looking at my photos collection I have close to 600 pictures taken. Singapore by day, Singapore by night, during the presentations at the Summit, and a couple of selfies with like-minded community folks. For the next few weeks I'm going to sieve through everything recorded and noted down, going to deepen those newly established connections, and looking forward to apply other creator's experience to my content creation process. Whether it will be more blog articles or fresh podcast episodes. To each and every one I met during the DCOC Summit: You're incredible and thank you for spending some time with me. I appreciate you. \uD83E\uDEF6 For now it's Goodbye community, goodbye Singapore, see you soon. Shout out to the organising team My heartfelt thanks go to Dirk Primbs, Alicja Heisig-Chiarello, Alessandro Palmieri, Monika Janota, Alfredo Morresi, Nathalie Pereira and the rest of the team at Google for organising such an amazing summit and bringing Developer Creators and organisers of Online Communities world-wide together in one place. Thank You! What a remarkable, first Developer Creators and Online Communities Summit it was. Well done! Image credits: Own snaphots and pictures taken by other creators and shared in an album on Google Photos. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'developer-creators-and-online-communities-summit.html'; this.page.identifier = '9271ed1c97_iddeveloper-creators-and-online-communities-summit'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devfest-dar-es-salaam-2023.html": {
    "href": "devfest-dar-es-salaam-2023.html",
    "title": "DevFest Dar es Salaam 2023 - Get Blogged by JoKi",
    "summary": "DevFest Dar es Salaam 2023 Copy Markdown View Markdown GitHub Markdown Source DevFest Dar es Salaam 2023 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-dar-es-salaam-2023'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-dar-es-salaam-2023'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'DevFest Dar es Salaam 2023') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-12-12 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After speaking at DevFest Nairobi in 2022 (including a spontaneous keynote on Google Cloud) I got inspired to be more active and hopefully present in other potential locations across the Sub-Saharan Africa region. And if I'm not completely wrong I also had an exchange with Georgia Rwechungura during the SSA Community Summit 2022 regarding the idea of coming to Tanzania, or more precisely to come to GDG Dar es Salaam and speaking at their DevFest. With an improving health and running a few workshops - in-house and remote - earlier this year I was getting more and more eager as well as excited about the idea to speak at other DevFest events than Mauritius. All went well at DevFest Kigali a few weeks earlier and so nothing could possibly stop me from going to Tanzania. Getting there and back? Unfortunately, there are not many connections between Mauritius and African countries, or at least there could and should be many more than there are at the moment. Without a direct flight I had a look at the available options for airlines, airports and the associated stopover times. Here are a few routes I checked out: Johannesburg, South Africa Nairobi, Kenya Antananarivo, Madagascar Dubai, UAE Mahe, Seychelles St. Denis, La Reunion It's really weird to travel in a yo-yo pattern like this. Meaning, you have to fly further than the actual destination and then \"come back\". A direct flight from Mauritius to Dar es Salaam would probably take less than three hours. Given the current possibilities it takes way more than twice the time. Anyway, I chose to go with Kenya Airways and having a night stopover in Nairobi. With access to their Pride of Africa lounge it wasn't too bad after all. Choice of accommodation Usually I try to find a hotel or guesthouse that's close to the DevFest venue and doesn't have the flair of a typical business trip. Thanks to Naamini Yonazi I knew that DevFest would most likely be somewhere on the campus of the University of Dar es Salaam which lies North of the airport and the city centre. Traffic might be dense during morning hours and the suggestions provided on the GDE trip planning platform were too far away and most of them not to my liking. Based on the positive experience I had with stays booked through the booking.com app in Europe I gave it a try for Tanzania. For those few days, I searched for an appropriate place to stay that is less than 30 minutes away from DevFest. Thanks to the ratings, great pictures, and other attributes like cost I actually found a hotel right on the beach. Surprisingly, it was also offered on the GDE trip planner but outside the suggested radius. With minor adjustments I got it as an option and was able to book without any issues. Winning! Travel companion Now, after seeing some pictures of the beach side, the pool, the rooms and the restaurants of the hotel my lovely BWE got very much interested in Tanzania. You might guess where this is heading to, don't you? Following our wedding anniversary celebrations in September my BWE and I talked about the possibility to spend a long weekend together in Tanzania, and we decided that would be a pleasant experience. Plus, Mary Jane already told me since a while that she would love to go there to check out tanzanite gemstones for a piece of jewelry she'd like to create. Getting around in Dar Planning to go to a new location, whether it's a new city, a new country, or both, involves checking out the options to get around. First problem to solve, how to get from the airport to the guesthouse and back, then how to go to and from the DevFest venue, and lastly how to explore other sights of importance while being there. Organising a ride from the airport to the guesthouse was the easiest. Thanks to the wonderful assistance of Naamini I had nothing to do. She arranged everything by asking her trusted driver to come to the airport to fetch us, and then drop us off at the guesthouse. She literally needed my flight details and destination only. Massive help and easy-peasy. During conversations at the reception desk it became pretty clear that either Uber or Bolt might be the best option to book rides. The recommendation was to use Bolt as it seems to be less expensive and more flexible than Uber. So we gave it a try the next day in order to get from the guesthouse to the venue of DevFest. BTW, we booked a car rather than a tuk-tuk (auto rickshaw). Later on, we felt more comfortable and went all in, either using Bolt rides or bargaining with the tuk-tuk drivers at their stand to agree on a fair. It was really good fun to discover places like this. Maasai Warriors Already back at the guesthouse where we stayed but also throughout other places we noticed that there were Maasai warriors around. Some were there to do hotel services like bell boy, some did gardening, and others seemed to be security for single, female travellers. Maybe a reader from Tanzania could help me out here. I found it really impressive! Stupidly, I forgot to ask to take a picture with at least one of them. Oh well, probably next time then. However I got myself a Maasai Shuka. Only need to learn and practice how to wear it though. The Venue: 孔子学院 Yup, that's the right place. Yes, I'm sure. Honestly, I had no idea of what to expect when I saw the DevFest location first time. But hey,why not? It kind of makes sense that there is a Confucius Institute in Dar es Salaam, Tanzania. Given the importance of the port and international trade with China I seems quite helpful for students to have easy access to courses on Chinese language and culture. And while writing this blog I discovered there is a 孔子学院 in Mauritius, too. Who'd have known? What's on the agenda of DevFest? Lots of amazing topics and sessions have been assembled by the GDG chapter in Dar es Salaam. Check out the full agenda on Sessionize to get an overview. Also, there had been multiple tracks in parallel allowing more speakers to present and give attendees a choice following their interest. Networking everywhere We managed to arrive a bit earlier than the times suggested on the agenda. This gave us time to get to know the team of GDG Dar a little bit, and to mingle with other attendees to get to know a bit more about their passions and activities. BTW, in one the rooms was even breakfast served. How cool is that? GDEs and a Googler Oh yes, you read this right. GDG Dar es Salaam managed to invite Khadija Juma from Google. As she is based in Mombasa, Kenya it seemed an obvious opportunity and I was really happy to introduce Mary Jane to her. As for GDEs, let me quickly list them in no particular order. Ezekias Bokove, GDE for Google Cloud Maye Edwin, GDE for Web Technologies Jochen Kirstätter, GDE for Google Cloud The highlights of DevFest Dar were the ice-breaker game that was done at the beginning as well as the group photo outdoors. The Confucius Institute is quite impressive and it was really good fun to spend the day with DevFest attendees. Thanks to GDG Dar es Salaam My heart-felt thanks go out to Naamini Yonazi, Georgia Rwechungura, the crew at GDG Dar es Salaam, and all WTM ambassadors involved in the preparation and organisation of DevFest Dar es Salaam. You are the real reason we had a wonderful time in Tanzania. Your early assistance and guidance helped us to feel welcome and at ease in Dar es Salaam. Asante sana! PS: We, Mary Jane and I, kind of already decided to visit Zanzibar during 2024, and we'd love to combine it with a future engagement with GDG Dar es Salaam, and who knows, maybe GDG Pwani in Kenya... Photo credits: Shared album on Pixieset by Sam Gidori, GDG Dar es Salaam. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devfest-dar-es-salaam-2023.html'; this.page.identifier = '9271ed1c97_iddevfest-dar-es-salaam-2023'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devfest-jinja-2023.html": {
    "href": "devfest-jinja-2023.html",
    "title": "DevFest Jinja 2023 - Get Blogged by JoKi",
    "summary": "DevFest Jinja 2023 Copy Markdown View Markdown GitHub Markdown Source DevFest Jinja 2023 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-jinja-2023'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-jinja-2023'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'DevFest Jinja 2023') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-12-28 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Uganda - a new destination to explore. My first time travelling to Uganda and it is for speaking at DevFest Jinja. Following the conversations with Francis Akol we arranged and got it approved that I would be able to travel as Google Developer Expert (GDE). A first for GDG Jinja. During several past occasions I met Francis, Fiona Nangobi (WTM ambassador in Jinja), and other community members from Uganda. Meaning, taking the trip to Jinja wouldn't be an adventure into the complete unknown but a welcoming experience with friends. In preparation I had the East Africa(n) Tourist Visa (EATV) in my passport which would fast-track me at the immigration officer, or at least that's what I thought would be the case; more on that below, I found valuable resources online which described where the ATMs would be located in the airport, and where to purchase a Ugandan SIM card. I chose Airtel due to recommendations regarding network coverage and quality. Like for my trip to Dar es Salaam two weeks ago, I decided to fly one or two earlier so that there be a chance for a bit of sightseeing. However this meant to be at the airport in the middle of the night, around 2:00 hrs in the morning catching the flight at 4:00 hrs then. As usual, the trip involved a stopover of approximately seven hours in Nairobi. While waiting in the Pride of Africa lounge I also seized the opportunity to clear a few issues I was facing with Asante Rewards, the frequent flyer loyalty program by Kenya Airways. To enter, or Not to enter that is the question: Whether 'tis nobler in the mind to suffer The slings and arrows of outrageous fortune, Or to take Arms against a Sea of troubles, ... It was not as dramatic as the opening speech of Prince Hamlet, however it was challenging nevertheless. Already back in Mauritius I was questioned about whether I had a VISA to enter Uganda, and again in Nairobi prior to boarding, I was quite surprised when I was told by the immigration officer that the EATV has expired. Hmm, hang on, I applied for it beginning of October and it's valid for 90 days, given multiple entry in either Rwanda, Kenya, or Uganda - so I thought. Well, there is an important detail which can be overseen quite easily or misunderstood as in my case. The visa is valid for 90 days and is not renewable upon expiry or upon exit from the block (Kenya, Uganda, Rwanda). The East Africa Tourist Visa (EATV) is an agreement between those three mentioned countries to ease transits between them for the given period. However the visa itself is single entry coming from any other country and (!) it expires as soon as you exit the defined realm. Ouch! I totally didn't see that back in October and it wasn't mentioned by the immigration officer in Kigali, Rwanda. Either way it was my fault or at least responsibility. With all the forth and back between immigration officer at the counter, direct supervisor, moving to the immigration offices at the back, and then speaking to the head supervisor about the situation, it crossed my mind that this could be a potential episode of Border Security. This is Africa, and there is always a way. Given my passport of European origin I was offered to apply for a new visa on arrival. Which I gratefully accepted. I mean paying USD $50 for my misunderstanding, read: the visa to enter Uganda, is a fair deal after all. Definitely better than sitting on the next plane back home. Let the real adventure begin After withdrawing some cash from one of the ATM and purchasing the Airtel SIM card I was ready for my first experience in Uganda. And what better to receive a hearty welcome by GDG fellow Asa Lugada and his friend Arnold. My connection to Asa even goes back to my first participation in the Andela-led ALC program, later GADS program. And of course the SSA Community Summit 2018. Some days earlier we discussed details about the airport transfer to the guest house I'm staying in Jinja. Of course, I could have a booked a taxi or an arrangement with the guest house but this premium community service was by far the better option. From Entebbe to Jinja is roughly 130km to drive. So we stopped at the Victoria Mall in Entebbe, got ourselves some snacks and refreshments, and off we went... Giving or taking it took us approximately five hours to the guesthouse in Jinja. Thanks to Google Maps we managed to find the place straight away. After check-in and inspection of the room we headed to a nearby Indian-Asian restaurant and we treated ourselves with some tasty delights. Later on I could finally enjoy a well-deserved shower and rest in bed. It was an exciting but also tiring day of travelling. Source of the Nile Arriving at least a day in advance has some advantages than just being there for the DevFest. I've seen this already with GDEs that came to speak at DevFest Mauritius, and I've done it travelling to DevFest Dar es Salaam previously. Francis suggested to meet first at my place, so that Marvin and Cynthia could deposit their stuff in my room, then a quick check of the venue and then seeing what we could do for the rest of the day. Weeks ago I mentioned that I would like to explore the source of the Nile which isn't too far from my local guesthouse anyways. It was an amazing excursion and learning that ashes of the late Mahatma Gandhi have been spread out here at source was an interesting note. I had a great day, good company and delicious food. Highly recommended! DevFest Jinja After getting some hours of sleep the big day had finally arrived. I enjoyed a nice local breakfast - called Rolex - and then called for a boda to get to The Innovation Village for DevFest Jinja. It was clearly way too early when I arrived at the venue. But well, better too early than too late, and therefore I took some time to talk to other GDG organisers, spoke to Fiona Nangobi, and was chit-chatting with Marvin and Cynthia about yesterday's excursion and other highlights. More and more people arrived and so Francis kicked off DevFest Jinja with a respectable crowd. A few minutes in the reception and registration area was swarmed with WTM ladies, already in their gowns for the dinner arrangement later that day. It was an amazing display of vibrant colours, great happiness and big smiles on the faces of all attendees. I was scheduled with two sessions. During the first half of the day, I spoke about the benefits of .NET and how implementations in C# as well as ASP.NET can be developed swiftly and deployed easily to various services offered by Google Cloud Platform. There were numerous questions about use cases of C# and how the available Google APIs can be accessed via NuGet packages. In the afternoon session I talked about the importance and advantages of professional certifications, in particular given the survey results that Google Cloud certifications are ranked among the highest paid ones. Build up your expertise and prove to yourself and others that you know your subject and that you are a knowledgable Google Cloud engineer. Those certification can open doors to higher payment on the job, better job offers and potentially a chance to work remotely with companies abroad. It was informative listening to the talks by other speakers. Feel free to check out the full agenda of DevFest Jinja to see what you eventually missed. With all those lovely people around, all the ongoing conversations and networking chats I somehow didn't realize that I had an exchange Zirabamuzale Rawaha aka RumzTech, organiser of GDG Luwero, with whom I did several remote sessions as part of the Road to Google Developers Certification earlier this year. Mea culpa, Rawaha! Ever tasted Nsenene? Purely by chance Asa and I were standing outside on the pavement trying to get a bit of fresh air and having an interesting conversation when this guy with an orange bucket walked pass us. As I wasn't sure whether I saw correctly I asked Asa about the content of the bucket. \"Ohh, you gotta taste this. Would you like to try?\" Yes, of course! Let's snack a handful roasted crickets - called nsenene in Luganda - with small chopped white onions. This species is a delicacy in central and south-western Uganda. That's what I'm looking for while travelling other countries. Experiencing new flavours, new delicacies and new smells. And actually, those crickets are yummy. If you have a chance to taste... do it. You might be surprised. On my way back home Unfortunately, trips to speak at DevFest events are quite short, and so it was already time to say good bye. Thankfully Asa and Arnold offered to provide transfer to the airport, and we had to start quite early in the morning already. Shortly before driving through the Mabira Central Forest Reserve we were greeted by a massive weather spectacle with mesmerizing lightnings and roaring thunders. Whew, the streets turned into small streams and here and there we were joking about paddling in a kayak rather than driving in the car. We made it well ahead of departure time, and even managed to stop at the Victoria Mall again to have some breakfast with tea. Another surprise was caused by Kenya Airways after we had landed in Nairobi. The scheduled flight to Mauritius had been delayed by another one and half hour so. Other travellers I met in the lounge were less lucky and were stranded for over twelve hours already. Eagerly hoping to continue their journeys. Shout out to GDG Jinja and WTM Jinja Congratulations to such a wonderful DevFest. Thank you so much for the warm welcome, the overwhelming care-taking and the joy I have experienced during those few days in Uganda. I'm really happy about the new impressions and experiences made. Personal thanks to Francis Akol, Asa Lugada and Fiona Nangobi! Keep shining all of you! Photo credits: Shared album on Google Photos by GDG Jinja. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devfest-jinja-2023.html'; this.page.identifier = '9271ed1c97_iddevfest-jinja-2023'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devfest-kigali-2023.html": {
    "href": "devfest-kigali-2023.html",
    "title": "DevFest Kigali 2023 - Get Blogged by JoKi",
    "summary": "DevFest Kigali 2023 Copy Markdown View Markdown GitHub Markdown Source DevFest Kigali 2023 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-kigali-2023'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-kigali-2023'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'DevFest Kigali 2023') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-11-15 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Again Rwanda, and GDG Kigali agreed to organise their DevFest at the same time as the annual SSA Community Summit. In comparison to last year I reached out to the organising commitee early and sent them a proposal to talk. The venue Given those circumstances bus transport from the hotel to the University of Rwanda, Gikondo Campus, has been organised and after a brief breakfast we went off. Luckily I was in the bus that drove to the right destination in first attempt. An earlier bus went the wrong direction but arrived we us at the same time. As usual, we were heading towards the registration desks to check in and to collect our badges. It took us a moment to gain orientation as there are several stores in the building. Our entrance was actually on level 2 and we had to go to ground level. Arriving there it was already packed with attendees and the team of GDG Kigali was heavily engaged welcoming everyone. Gladly, the check-in for speakers was separately and all went very smooth and quickly. Thanks guys! Entering the theatre GR (Ground Right) brought back memories from my times at the Technical University of Kaiserslautern with its large auditoriums. It would seat approx. 300 attendees. Hopefully enough space for all those technology interested participants. As the agenda had been shared and my talk was scheduled after the lunch break, I was quite relaxed during the whole morning and I enjoyed listening to the various talks from other speakers. Just in case, the complete agenda is accessible here. SpaceX Starlink So while kind of chilling during the morning hours I checked the bandwidth and speed of the provided internet connection. Interestingly, the university is using SpaceX Starlink. First time using SpaceX Starlink. Getting better... \uD83D\uDE01#DevFestKigali https://t.co/e17EqbAhgw — Jochen Kirstätter (JoKi) (@JKirstaetter) October 7, 2023 The speedtest.net results look pretty good. Better than connectivity offered back home. Supposedly, Starlink is coming soon to Mauritius. With so much bandwidth at hands what better to do than chasing around and collecting numerous selfies? Yeah, let's go hunting... Can you name everyone in the shots above? Apart from taking pictures I had some really good conversations with attendees regarding Google Cloud Platform, the right choice of technology stack to develop application XYZ (which obviously depends), personal growth and career advice, and about the island of Mauritius in general. State of GCP: .NET edition Right after the lunch break my session had been scheduled in the main auditorium. It's a tough slot and as a speaker you have to try to keep your audience from falling into a well-deserved food-coma, read: after-lunch-nap. It is interesting to realise that the majority of the audience is not familiar with .NET. Given that it is a Google focused event I can understand. Even more interesting is it then to observe the changes happening during my talk. As soon as I'm starting to show the first snippets of source code it seems there is a shift in attention. Some of the pain-points I mention regarding other technology stacks compared to how it can be handled in .NET, respectively written the code in C#. Perhaps use of third-party libraries and package management is one of those hot topics which are, in my opinion, easier to handle in .NET using NuGet compared to Node.js and node modules. Especially as soon as it comes to upgrading versions in longer running projects. More sessions Throughout the day there had been two periods with so-called breakout sessions were six presentations were held in parallel. It's actually an interesting concept to shift attendees based on their interest while keeping the focus in the main auditorium when needed. Finally, although I didn't attend, I'd like to mention that there had been a designated breakfast session for Women in Tech (WIT) and Women TechMakers (WTM). It's great to see this level of consciousness during DevFest Kigali and the dedication to make it comfortable as well as welcoming for women in technology to come, to participate and to network. My gratitude to GDG Kigali Clearly, there is so much potential in Rwanda, perhaps in whole Africa most likely, and participating in a DevFest event being asked for advice, asking a few questions to hopefully stimulate attendees to shift their view on their situation slightly, as well as sharing from my own experience is so rewarded. I'm really grateful for this opportunity to attend and speak at DevFest Kigali. Big thanks to Josue, Jean Salvi, and the whole team of GDG Kigali. You guys rock! Keep doing community and shine. Lastly, I'm hoping to come back to Kigali another day (or two). It was again amazing. Photo credits: Shared Photos album by SSA Community Leads and Flicker album by GDG Kigali if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devfest-kigali-2023.html'; this.page.identifier = '9271ed1c97_iddevfest-kigali-2023'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "devfest-mauritius-2023.html": {
    "href": "devfest-mauritius-2023.html",
    "title": "DevFest Mauritius 2023 - Get Blogged by JoKi",
    "summary": "DevFest Mauritius 2023 Copy Markdown View Markdown GitHub Markdown Source DevFest Mauritius 2023 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-mauritius-2023'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'devfest-mauritius-2023'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'DevFest Mauritius 2023') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-12-07 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Speaking on home turf is an exciting situation. You know the venue(s), you probably know the audience of the event, and overall, preparations are usually completed a few days or even weeks ahead of time. Nonetheless, it's still very exciting. Since 2018 organised the local GDG chapter in Mauritius the annual DevFest, with the exception in 2021 due to COVID related restrictions, and it is a well received opportunity to get your head into the latest technologies offered by Google. Whether it is mobile, web, cloud, AI, or machine learning (ML) the topics are wide spread and the range of speakers is great. The venue: University of Mauritius GDG Mauritius is the only chapter on the island due the small dimensions and population here, and every year DevFest is held at a new location. So this year, we teamed up with students at the University of Mauritius (UoM) to handle all administrative task to secure a spot on their campus. Luckily, there is the Google Developers Student Club (GDSC) UoM which assisted big time with the communication towards responsible parties at the university and on choosing the right location(s) on the campus. DevFest is community first With this approach in mind, all preparational and organisational steps have been worked out together with all four, currently active GDSC chapters in Mauritius. Which are in no particular order GDSC University of Mauritius GDSC African Leadership College GDSC University of Technology GDSC Polytechnics Mauritius Ltd Please check out their chapter pages and see their activities throughout the year. Personally, I really love this kind of collaborative work. It helps to delegate various tasks and the input received from various students was valuable for the GDG Mauritius. Plus, as GDG organiser I'm also hoping to attract some of the students to continue their user group engagements after graduating and leaving their GDSC chapter in the future. Highlight #1 - Over 250 attendees! What an outstanding number. Finally we are back to the attendance as before the pandemic and it was a buzzling DevFest. People started to come while the organising team was setting up the registration desk. Note: we are usually too early. It was a joy to see all the networking interactions. Students from different campuses mingled with seasoned professionals. Our speakers were present and had constantly conversations with attendees - before their respective and surely afterwards. Really great to have in-person conferences happening. Highlight #2 - GDEs! This has been my favourite highlight of DevFest. Thanks to the generous support of the GDE travel grant program the organising team managed to bring a whooping four Google Developer Experts (GDE) to Mauritius. Let me quickly give you their names and links to their social media profiles. Wayne Gakuo, GDE Angular, Kenya Vivek Yadav, GDE Flutter, India Vrijraj Singh, GDE Firebase & Web Technologies, India Sylvia Dieckmann, GDE Flutter, South Africa Jochen Kirstätter, GDE Google Cloud, Mauritius Sandeep Ramgolam, GDE Web Technologies, Mauritius I'm also very happy about our two guests from India, meaning they are GDEs from outside the Sub-Saharan Africa (SSA) region. Which is an exceptional handling on the GDE program's side. Many thanks for this unique experience. Lots of interesting talks Thanks to the high interest of speakers and submissions of proposals the agenda of DevFest Mauritius 2023 was packed with interesting topics. All details and the full agenda plus information about all speakers is available on the event page. DevFest Mauritius 2023 And alternatively on theCommunity Dev event page. Here can also join the GDG Mauritius and stay informed about future activities and events. Thanks everyone involved Many thanks to our DevFest host - University of Mauritius -, represented by Dr Baby Gobin-Rahimbux and Mrs Anuja Meetoo-Appavoo. To the helping staff of UoM before and during the event. Thank you! Shout out to all GDSC members involved in the DevFest preparations in all kind of ways. You all did a great job and contributed to the success of this year's event. Thanks! Respect and heartfelt thanks to all speakers. And attendees. For spending your precious spare time with other like-minded IT and tech people here at DevFest Mauritius. I hope you DevFest was a worthy experience. Really appreciated and you're the driving force for more. Thanks! Many thanks to our partners Google, Liquid Intelligent Technologies and IOS Indian Ocean Software Ltd for financial assistance and adminstrative handling. The goodies have been well received and I think the pin badge makes a huge difference... Thanks! And lastly kudos to the \"core\" organising team of GDG Mauritius for pulling it off once again. And with this bombshell, I'm looking forward to DevFest 2024. Photo credits: Shared album on Photos with wonderful images contributed by other attendees of DevFest Mauritius 2023. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'devfest-mauritius-2023.html'; this.page.identifier = '9271ed1c97_iddevfest-mauritius-2023'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "dfpug-bietet-blogs-fuer-mitglieder.html": {
    "href": "dfpug-bietet-blogs-fuer-mitglieder.html",
    "title": "dFPUG bietet Blogs für Mitglieder - Get Blogged by JoKi",
    "summary": "dFPUG bietet Blogs für Mitglieder Copy Markdown View Markdown GitHub Markdown Source dFPUG bietet Blogs für Mitglieder Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dfpug-bietet-blogs-fuer-mitglieder'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dfpug-bietet-blogs-fuer-mitglieder'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'dFPUG bietet Blogs für Mitglieder') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-23 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Tjoaja, es ist erfreulich, dass inzwischen wieder mehr Leben in den Webangeboten der deutschsprachigen FoxPro Usergroup zu vernehmen ist. Seit der Entwicklerkonferenz hat sich online einiges getan. Neben der Aktualisierung der LiveFaq auf die aktuelle Fassung des AfpWiki, hat auch Gerd zusammen mit Peter seine eigene Blogsoftware zur Verfügung gestellt und den Mitgliedern der dFPUG steht damit ein eigenes, kostenfreies Weblog zur Verfügung. Also, wenn das nicht ein feines Angebot ist... Zum gegenwärtigen Zeitpunkt gibt's noch kleinere Abweichungen in den Verhalten der unterschiedlichen Systeme, aber ich gehe davon aus, dass wir drei Entwickler diese Unpässlichkeiten noch beheben werden. Ich hoffe, dass ich die beiden anderen mit dem Quellcode aus dem AfpWiki und der Funktionalität des HTML Plugins überzeugen kann... Nicht nur, dass wir dann ziemlich zeitgleich die Entwicklung vorantreiben können, sondern auch wegen der Tatsache, dass das AfpWiki aktuell den größten Umfang für das Parsen der Eingaben besitzen dürfte. Hier seien nur kurz ein paar genannt: SyntaxHighlighting für Quellcode (VFP & C#), BBCode-Interpretation, Smilies, etc... Wir werden sehen, was geht... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'dfpug-bietet-blogs-fuer-mitglieder.html'; this.page.identifier = '9271ed1c97_iddfpug-bietet-blogs-fuer-mitglieder'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "dhcpv6-ipv6-in-your-local-network.html": {
    "href": "dhcpv6-ipv6-in-your-local-network.html",
    "title": "DHCPv6: Provide IPv6 information in your local network - Get Blogged by JoKi",
    "summary": "DHCPv6: Provide IPv6 information in your local network Copy Markdown View Markdown GitHub Markdown Source DHCPv6: Provide IPv6 information in your local network Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dhcpv6-ipv6-in-your-local-network'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dhcpv6-ipv6-in-your-local-network'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'DHCPv6: Provide IPv6 information in your local network') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-03-11 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Even though IPv6 might not be that important within your local network it might be good to get yourself into shape, and be able to provide some details of your infrastructure automatically to your network clients. This is the second article in a series on IPv6 configuration: Configure IPv6 on your Linux system DHCPv6: Provide IPv6 information in your local network Enabling DNS for IPv6 infrastructure Accessing your web server via IPv6 Piece of advice: This is based on my findings on the internet while reading other people's helpful articles and going through a couple of man-pages on my local system. IPv6 addresses for everyone (in your network) Okay, after setting up the configuration of your local system, it might be interesting to enable all your machines in your network to use IPv6. There are two options to solve this kind of requirement... Either you're busy like a bee and you go around to configure each and every system manually, or you're more the lazy and effective type of network administrator and you prefer to work with Dynamic Host Configuration Protocol (DHCP). Obviously, I'm of the second type. Enabling dynamic IPv6 address assignments can be done with a new or an existing instance of a DHCPd. In case of Ubuntu-based installation this might be isc-dhcp-server. The isc-dhcp-server allows address pooling for IP and IPv6 within the same package, you just have to run to independent daemons for each protocol version. First, check whether isc-dhcp-server is already installed and maybe running your machine like so: $ service isc-dhcp-server6 status In case, that the service is unknown, you have to install it like so: $ sudo apt-get install isc-dhcp-server Please bear in mind that there is no designated installation package for IPv6. Okay, next you have to create a separate configuration file for IPv6 address pooling and network parameters called /etc/dhcp/dhcpd6.conf. This file is not automatically provided by the package, compared to IPv4. Again, use your favourite editor and put the following lines: $ sudo nano /etc/dhcp/dhcpd6.conf authoritative; default-lease-time 14400; max-lease-time 86400; log-facility local7; subnet6 2001:db8:bad:a55::/64 { option dhcp6.name-servers 2001:4860:4860::8888, 2001:4860:4860::8844; option dhcp6.domain-search \"ios.mu\"; range6 2001:db8:bad:a55::100 2001:db8:bad:a55::199; range6 2001:db8:bad:a55::/64 temporary; } Next, save the file and start the daemon as a foreground process to see whether it is going to listen to requests or not, like so: $ sudo /usr/sbin/dhcpd -6 -d -cf /etc/dhcp/dhcpd6.conf eth0 The parameters are explained quickly as -6 we want to run as a DHCPv6 server, -d we are sending log messages to the standard error descriptor (so you should monitor your /var/log/syslog file, too), and we explicitely want to use our newly created configuration file (-cf). You might also use the command switch -t to test the configuration file prior to running the server. In my case, I ended up with a couple of complaints by the server, especially reporting that the necessary lease file wouldn't exist. So, ensure that the lease file for your IPv6 address assignments is present: $ sudo touch /var/lib/dhcp/dhcpd6.leases $ sudo chown dhcpd:dhcpd /var/lib/dhcp/dhcpd6.leases Now, you should be good to go. Stop your foreground process and try to run the DHCPv6 server as a service on your system: $ sudo service isc-dhcp-server6 start isc-dhcp-server6 start/running, process 15883 Check your log file /var/log/syslog for any kind of problems. Refer to the man-pages of isc-dhcp-server and you might check out Chapter 22.6 of Peter Bieringer's IPv6 Howto. The instructions regarding DHCPv6 on the Ubuntu Wiki are not as complete as expected and it might not be as helpful as this article or Peter's HOWTO. But see for yourself. Does the client get an IPv6 address? Running a DHCPv6 server on your local network surely comes in handy but it has to work properly. The following paragraphs describe briefly how to check the IPv6 configuration of your clients, Linux - ifconfig or ip command First, you have enable IPv6 on your Linux by specifying the necessary directives in the /etc/network/interfaces file, like so: $ sudo nano /etc/network/interfaces iface eth1 inet6 dhcp Note: Your network device might be eth0 - please don't just copy my configuration lines. Then, either restart your network subsystem, or enable the device manually using the dhclient command with IPv6 switch, like so: $ sudo dhclient -6 You would either use the ifconfig or (if installed) the ip command to check the configuration of your network device like so: $ sudo ifconfig eth1 eth1 Link encap:Ethernet HWaddr 00:1d:09:5d:8d:98 inet addr:192.168.160.147 Bcast:192.168.160.255 Mask:255.255.255.0 inet6 addr: 2001:db8:bad:a55::193/64 Scope:Global inet6 addr: fe80::21d:9ff:fe5d:8d98/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 Looks good, the client has an IPv6 assignment. Now, let's see whether DNS information has been provided, too. $ less /etc/resolv.conf # Dynamic resolv.conf(5) file for glibc resolver(3) generated by resolvconf(8) # DO NOT EDIT THIS FILE BY HAND -- YOUR CHANGES WILL BE OVERWRITTEN nameserver 2001:4860:4860::8888 nameserver 2001:4860:4860::8844 nameserver 192.168.1.2 nameserver 127.0.1.1 search ios.mu Nicely done. Windows - netsh Per description on TechNet the netsh is defined as following: \"Netsh is a command-line scripting utility that allows you to, either locally or remotely, display or modify the network configuration of a computer that is currently running. Netsh also provides a scripting feature that allows you to run a group of commands in batch mode against a specified computer. Netsh can also save a configuration script in a text file for archival purposes or to help you configure other servers.\" And even though TechNet states that it applies to Windows Server (only), it is also available on Windows client operating systems, like Vista, Windows 7 and Windows 8. In order to get or even set information related to IPv6 protocol, we have to switch the netsh interface context prior to our queries. Open a command prompt in Windows and run the following statements: C:\\Users\\joki>netsh netsh>interface ipv6 netsh interface ipv6>show interfaces Select the device index from the Idx column to get more details about the IPv6 address and DNS server information (here: I'm going to use my WiFi device with device index 11), like so: netsh interface ipv6>show address 11 Okay, address information has been provided. Now, let's check the details about DNS and resolving host names: netsh interface ipv6>show dnsservers 11 That looks good already. Our Windows client has a valid IPv6 address lease with lifetime information and details about the configured DNS servers. Talking about DNS server... Your clients should be able to connect to your network servers via IPv6 using hostnames instead of IPv6 addresses. Please read on about how to enable a local named with IPv6. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'dhcpv6-ipv6-in-your-local-network.html'; this.page.identifier = '9271ed1c97_iddhcpv6-ipv6-in-your-local-network'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "die-bahn-ist-pnktlich.html": {
    "href": "die-bahn-ist-pnktlich.html",
    "title": "Die Bahn ist pünktlich... - Get Blogged by JoKi",
    "summary": "Die Bahn ist pünktlich... Copy Markdown View Markdown GitHub Markdown Source Die Bahn ist pünktlich... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-bahn-ist-pnktlich'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-bahn-ist-pnktlich'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Die Bahn ist pünktlich...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-09 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); ...im Vergleich zur Czech Airlines Fluglinie. Da quält man sich früh morgens unter die Dusche, verzichtet aus Zeitmangel auf das üppige Frühstück im Hotel, um sich eine fette Verzögerung beim Flug nach Prag einzufangen. Na ganz toll. \uD83E\uDD2F CheckIn, Passkontrolle, alles wunderbar, fehlt jetzt nur noch der Flieger... und der ist nicht da und als noch nicht, und achja, immer noch nicht. Naja, im Laufe der Zeit gibt's dann nette Ansagen wie 'verspätet sich um etwa 30 Minuten', 'immer noch nicht im Anflug, die Abreise verzögert sich um 45 Minuten' oder 'der Gegenflug nach Berlin ist wegen eines mechanischen Problems noch nicht gestartet, die erwartete Abflugzeit ist etwa in 1:30 Stunden'. Beobachtung in einer Menschensammelstelle - die Boardingzone Die Boardingzone kann auf Dauer recht öde werden, auch wenn da ein Duty Free Shop offen hat. Naja, machen wir das sinnvollste draus und schauen mal, was denn anderen Flügen um diese Uhrzeit geht und welche Fluggäste so unterwegs sind. Mal abgesehen von einigen genervten Müttern samt weinenden Kindern stelle ich mir dann doch die folgende Frage: \"Wie kann man sich morgens um etwa 10:00 Uhr die 0,5l Bierdosen in den Kopp ziehen?\" - Hm, ob da evtl. Flugangst mit bei ist? Egal, auf alle Fälle ein reges Treiben hier. Interessant auch der Bereich um den Metalldetektor; und die häufigen Ausrufe nach eingecheckten Fluggästen. Herrje, schläft man etwa ein? Oder ist die Anzeigetafel für das Boarding zu eindeutlich? An der Beschallung im gesamten Flughafen kann's sicherlich auch nicht liegen. Dennoch hatte ich den Eindruck (oder Verdacht), dass mancher Flug ohne einen ausgerufenen Passagier abhob. Trotz des ständigen Kommen und Gehen anderer Menschen liess sich mit der Zeit klar ausmachen, wer auf den heißersehnten Flug in die tschechische Hauptstadt wartete... \uD83D\uDE01 Das andere Links... Beim Boarding bin ich dann auch mit zwei Amerikanern ins Gespräch gekommen. Sie seien bereits seit etwa 7:00 Uhr hier, um den Abflug zu warten. Puuh, das ist mal locker 'ne Stunde mehr als ich bereits habe. Apropos Boarding. Irgendwie scheint die nette Dame beim Ticketabriss eine leichte Rechts-Links-Schwäche zu haben. \"Down the stairs and take the bus on the left side.\" - Würde mir auch stinken, ständig das gleiche von mir zu geben. Zwischendurch kam dann eine leichte Variante: \"It's the bus line number 194 on the left side.\" Und nach einem freundlichen Guten Morgen von mir, lächelte sie lediglich und wünschte mir einen angenehmen Flug. Jaaa, ich hab's mitbekommen, dass der Bus 194 die Treppe runter links steht. \uD83E\uDD2A Nur... erstens stehen da zwei Busse, zweitens hat der linke die Nummer 192 und einige bekannte Gesichter des Morgens haben dem Transport mit dem Bus auf der anderen links Seite den Vorzug gegeben. Ganz im Sinne des Herdentriebs folge ich in die Nummer 194 auf der rechten Seite - okay, wenn man die Treppe hoch gehen würde, dann stimmt es ja mit dem links. Bus 192 fährt und kurze Zeit eilt er doch ein zügiger wieder zurück: Fahrgastwechsel - ja ja, links... \uD83D\uDE44 Die Rettungsweste befindet sich... ... die was? \uD83E\uDD2A - Also, liebe Fluggesellschaften, ihr solltet euch wirklich mal was Besseres in der Ausbildung eurer Flugbegleiter überlegen. Welche Sinn sollte eine Schwimm- äh Rettungsweste auf einem innereuropäischen Flug haben? Die Wahrscheinlichkeit, dass wir mit dieser zweimotorigen Propellermaschine (eine ATR-72) in einem größeren Gewässer abgehen sollte, ist geschätzterweise sehr, sehr gering, aber okay. Für ein Stunde Flugzeit sicherlich lebenswichtig. Endlich wieder in der Luft Ahhh, jetzt weiß ich, was ich die letzten Jahre vermisst habe. Endlich mal wieder in der Luft unterwegs. Herrlich, die Sonne lachte uns zwar bereits am Boden an, aber allein der Aufstieg durch die Wolken ist ein herrlicher Anblick - wurde ich doch echt beim Checkin gefragt, ob Fenster oder nicht... Natürlich Fenster, am besten Panorama, was denn sonst? Frauen kann ich am Boden ausreichend genug sehen, und die Optik vom Fluggang; kein Kommentar. Zeitliche Proportionen oder die Geschwindigkeit macht's Unsere Flugzeit betrug in etwa 1 Stunde, die 'Dschungelreise' quer durch Prag kommt knapp über 2 Stunden. Okay, nicht weiter verwunderlich, wenn man wirklich einmal quer - Flughafen im Nordwesten und Hotel im Südosten - durch die City muss. Bus, Metro A, Metro C und wieder Bus. Durch die Erfahrung meines Besuches 2005 habe ich mir auch gleich eine 7-Tageskarte besorgt. Damit brauche ich mir dann keine Gedanken mehr bzgl. Fahrkarte in öffentlichen Verkehrsmittel machen. Und mal sehen, ob ich die Karte nicht doch noch am Mittwoch morgen als 3-Tageskarte verkaufen kann. Wir werden sehen, ob was geht... \uD83D\uDE01 Welcome to the largest hotel complex in Europe und kürzer formuliert: TOP HOTEL - 1020 Zimmer und die üblichen Annehmlichkeiten. Nett, wirklich nett hier. Die Aussicht aus meinem Zimmer entspricht dem Prospektbild mit Blick hinaus auf den inneren Bereich der Hotelanlage: japanischer Garten mit Wasserlandschaft. Später mal spazieren gehen. Auch das Angebot für Wellness und Erholung lädt direkt auf eine nähere Inspektion ein. Soweit mal mein aktueller Reisebericht. Mehr die Tage wieder und bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'die-bahn-ist-pnktlich.html'; this.page.identifier = '9271ed1c97_iddie-bahn-ist-pnktlich'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "die-cebit-ruft---.html": {
    "href": "die-cebit-ruft---.html",
    "title": "Die CeBIT ruft... - Get Blogged by JoKi",
    "summary": "Die CeBIT ruft... Copy Markdown View Markdown GitHub Markdown Source Die CeBIT ruft... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-cebit-ruft---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-cebit-ruft---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Die CeBIT ruft...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-10 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Auch dieses Jahr lädt Microsoft sein CLIP-Mitglieder zum Community GetTogether in die niedersächsische Hauptstadt ein. Und ich habe den Ruf wieder einmal vernommen. Selbstverständlich geht's mit Sack und Pack, öhem... Laptop und Rucksack auf in den Norden. Spontane Aktionen sind an der Tagesordnung, denn irgendwie hat mal wieder die Frühbuchung für 'ne Übernachtung nicht hingehauen, da berufliche Aufgaben in den letzten Wochen im Vordergrund standen. Well, who cares? Ein Plätzchen zum Nächtigen findet sich in der kleinsten Hütte und wir lassen uns einfach mal überraschen... \uD83D\uDE09 Nach der Mittagspause trifft mein persönlicher Chauffeur in der Firma ein und nach einem kurzen Abmeldungstechtelmechtel geht's auf die Autobahn. Hehe... und ich hab' die UMTS/GPRS-Karte dabei. Und die Kante Kirchheimbolanden passiert uns irgendsoein Rennzwerg - hey, Formel 1 ist zwar wieder gestartet, aber so kleine Rennboliden mit einer derartigen Lackierung können wir uns definitiv nicht vorstellen. Und in der Tat... Es handelt sich um einen Erlkönig der Marke smart. Und das Teil ist flink, bei ca. 160km/h vergrößert sich langsam der Abstand... nice try. Vorbei an Frankfurt geht's flockig auf die A5 und Stau... Hmpf! Nun, okay, schauen wir uns mal die Sache im Überblick an - Laptop und UMTS-Karte gezückt, Staumelder online abfragen - cooles Feature. Sollte zukünftig in alle Autos Standardausstattung sein. Nunja, noch ein paar Rückfragen per Messenger und Skype geklärt - herrje, so wichtig bin ich doch gar nicht... Und nach Passage der Unfallstelle testen wir mal die Onlineconnection bei ca. 150km/h - die Frisur hält! Dennoch haben wir durch den Stau einiges an Zeit verloren, was wir nur schlecht wett machen können. Hm, das dürfte mit der Tourist Information und Zimmervermittlung in Hannover knapp werden. Und in der Tat ist der Laden bereits geschlossen... trotz verlängerter Öffnungszeiten. Nun, no problem, zuerst mal ab in die Community - vielleicht ergeben sich ja hier noch Möglichkeiten und Chancen... zumindest gibt's was zu futtern, nette Leute und viel Spass. Das haben wir uns nach dem Autobahn-Marathon auch ein wenig verdient. Letztendlich haben wir dann doch eine coole Unterkunft in Großbuchholz finden können: https://www.hotel-finkenhof.de - nur traut den Preisen auf der Website nicht... Das kommt noch der undokumentierte Messeaufschlag hinzu... Ich werde mir auf alle Fälle noch eine kleine Mail in den nächsten Tagen zurecht schreiben und absenden. Egal, wir hatten Betten und ein Dach über den Köpfen - das allein zählte. Zumindest konnte ich dann noch nachts um 2:00 direkt ein, zwei Fragen im dFPUG-Forum beantworten \uD83D\uDE42 Der morgige Tag wird umfangreich, also Gute Nacht, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'die-cebit-ruft---.html'; this.page.identifier = '9271ed1c97_iddie-cebit-ruft---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "die-checkbox-und-ihr-enter-return-verhalten.html": {
    "href": "die-checkbox-und-ihr-enter-return-verhalten.html",
    "title": "Die Checkbox und ihr Enter/Return-Verhalten - Get Blogged by JoKi",
    "summary": "Die Checkbox und ihr Enter/Return-Verhalten Copy Markdown View Markdown GitHub Markdown Source Die Checkbox und ihr Enter/Return-Verhalten Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-checkbox-und-ihr-enter-return-verhalten'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-checkbox-und-ihr-enter-return-verhalten'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Die Checkbox und ihr Enter/Return-Verhalten') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-08-17 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Das Problem Heute mal wieder etwas aus der Programmierküche mit dem Fuchs. Ausgehend von einer Anforderung in einem laufenden Projekt sollte es ermöglicht werden, dass man innerhalb eines VFP Grids sowohl mit der Tab-Taste wie auch der Enter-Taste durch die Zellen navigieren kann. Nun, soweit stellt dies eigentlich kein Problem dar. Interessant wird es erst im Zusammenspiel mit anderen Klassen als Control der Spalte. Etwa der Checkbox. Das normale Verhalten der Checkbox bei Tasteneingaben in Visual FoxPro sieht kurz gesagt so aus: Space - Änderung des Zustands Tab - Wechsel zum nächsten Control ohne Statusänderung Enter/Return - Wechsel zum nächsten Control mit Änderung des Zustands Nun gut, damit kann man leben und das Verhalten erscheint auf den ersten Blick intuitiv. Nur... wir wollen innerhalb des Grid nur Navigieren und nicht den Zustand der Checkbox verändern. Die erste Idee Jedem Fuchs wird auf Anhieb einfallen, dass die Lösung des Problems in der Überlagerung der Checkbox.KeyPress() Ereignismethode zu realisieren. Das ist auch vollkommen korrekt und okay. Mein erster Ansatz ergab sich aus einer kurzen Recherche samt Bestätigung im FoxWiki. Der Quellcode sah im ersten Ansatz so aus: *==================================================================== * Erweiterung: Die Return/Enter-Taste soll keine Funktion besitzen. * Das Problem ist, dass primär mit der Enter- neben der * Tab-Taste in den Formularen und Grids navigiert wird. *==================================================================== LPARAMETERS nKeyCode, nShiftAltCtrl If (m.nKeyCode == 13 .And. InList(m.nShiftAltCtrl, 0, 1)) .Or. ; (m.nKeyCode == 10 .And. m.nShiftAltCtrl == 2) Keyboard '{TAB}' NoDefault Return EndIf DoDefault() Wir verändern das Verhalten der Checkbox soweit, dass die Enter/Return-Taste ihre Funktionalität verliert und der Wechsel durch das Auslösen der Tab-Taste wiederhergestellt wird. Ein kurzer Test mit der erweiterten Klasse auf einem Formular funktioniert soweit tadellos. Fein... Das Extra - VFP Grid Frohen Mutes wird diese Klasse nun als CurrentControl in eine Gridspalte eingebaut und getestet. Das Ergebnis ist katastrophal! Neben der Tatsache, dass der Zustand der Checkbox geändert wird, wechseln wir nicht nur in die nächste Spalte, nein, sondern in die übernächste. Na ganz toll... Nun, was ändert sich den wirklich? Beim Steppen im Debugger ergibt sich, dass das Checkbox.Value nicht verändert wird. Soweit entspricht dies der Verwendung auf einem Formular oder innerhalb eines Containers. Aber... wir haben eine ControlSource und anscheinend verhält sich VFP in der Art, dass zwar der Wert der Checkbox unverändert bleibt, aber trotzdem die ControlSource geändert wird und aus der Aktualisierung heraus, wechselt dann die Checkbox doch ihren Wert. Interessanterweise passiert dies nicht, wenn die Checkbox mit eigener ControlSource genutzt wird. Da ich keine Möglichkeit gefunden habe, die Wertänderung in der Column.ControlSource zu verhindern, nutze ich die 'Technik der doppelten Negation' \uD83D\uDE0E um zum gewünschten Ergebnis zu kommen. Innerhalb des KeyPress prüfe ich auf die Existenz des Parent von der Basisklasse 'Column' und verändere den aktuellen Wert der Checkbox. Da VFP ebenfalls eine Invertierung des Value durchführt, enden wir wieder beim ursprünglichen Zustand. Außerdem vermeiden wir das 'Drücken' der Tab-Taste, da wir ja in die nächste Spalte wechseln wollen... Die Lösung Und zusammengetippt sieht die KeyPress-Methode inzwischen folgendermaßen aus: *==================================================================== * Erweiterung: Die Return/Enter-Taste soll keine Funktion besitzen. * Das Problem ist, dass primär mit der Enter- neben der * Tab-Taste in den Formularen und Grids navigiert wird. *==================================================================== LPARAMETERS nKeyCode, nShiftAltCtrl With This Local llColumn, lcControlSource, lcVartype, luValue m.llColumn = .F. m.lcControlSource = \"\" m.lcVartype = \"\" m.luValue = 0 If .lSkipEnter .And. ( ; (m.nKeyCode == 13 .And. InList(m.nShiftAltCtrl, 0, 1)) .Or. ; (m.nKeyCode == 10 .And. m.nShiftAltCtrl == 2) ; ) m.llColumn = (Vartype(This.Parent) == T_OBJECT .And. ; Lower(This.Parent.BaseClass) == \"column\") *--- Falls die Checkbox ein Child einer Column ist, *--- müssen wir uns um die ControlSource kümmern. If m.llColumn m.lcControlSource = This.Parent.ControlSource m.luValue = Evaluate(m.lcControlSource) m.lcVartype = Vartype(m.luValue) .Value = ICase( ; m.lcVartype == T_LOGICAL, Not m.luValue, ; 1 - m.luValue ; ) Else *--- Ansonsten brauchen wir den zusätzlichen TAB *--- nicht auszulösen. Keyboard '{TAB}' EndIf NoDefault Return EndIf EndWith DoDefault() Der Code ist in der ersten Ableitung implementiert und durch das Setzen der Checkbox.lSkipEnter kann sowohl in Ableitungen wie auch Instanzen der Checkbox das Verhalten reguliert werden. Interessant ist wie der Fuchs wieder die ganze Sache handhabt, aber okay... es ist nachvollziehbar. Welche Tricks und Erfahrungen habt ihr mit der Checkbox bisher gemacht? Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'die-checkbox-und-ihr-enter-return-verhalten.html'; this.page.identifier = '9271ed1c97_iddie-checkbox-und-ihr-enter-return-verhalten'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "die-happy-a-culcha-candela.html": {
    "href": "die-happy-a-culcha-candela.html",
    "title": "Die Happy & Culcha Candela - Get Blogged by JoKi",
    "summary": "Die Happy & Culcha Candela Copy Markdown View Markdown GitHub Markdown Source Die Happy & Culcha Candela Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-happy-a-culcha-candela'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-happy-a-culcha-candela'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Die Happy & Culcha Candela') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-27 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Glücklich sterben... ...ja, das würde sicherlich jedem gefallen. So mitten im Org... \uD83E\uDD2A Aber noch ist es nicht soweit und wir ziehen unsere Aufmerksamkeit vom Verlassen der sterblichen Hülle und der Freisetzung des Was-auch-immer-man-es-nennen-möchte (kurz: ES) auf die interessanten Dinge des Lebens: Musik. Die Happy ist eine deutsche Rockgruppe mit sehr ambitionierter Musik. Ich muss gestehen, dass ich mir seit letzten Montag deren Alben beim Coden auf die Lauscher gezogen habe. Richtig entspannt! Kam schon ziemlich leger rüber, am Arbeitsplatz vorm PC zu lungern, der Mucke von Die Happy zu fröhnen und lockerflockig die VFP Codezeilen in die Tastatur zu hackn. Ist sogar richtig produktiv gewesen. Zusätzlich haben die Ohrknöpfe noch eine nette Signalwirkung für meine Kollegen: Please, do not disturb. Coder @ Work. Also, hört euch mal die Alben von Die Happy an, meines Erachtens ein Genuß deutscher Rockmusik. Achja, morgen erscheint deren neues Album - ich bin mal gespannt. So, wie schafft man den Übergang vom glücklichen Sterben hinüber zum puren Genießen des Leben in vollen Zügen? Ich weiß es acuh nicht, daher probiere ich es erst gar nicht... \uD83D\uDE0E Culcha Candela - sinngemäß mit leuchtende Kultur übersetzt - beginnen am deutschen Musikhimmel zu leuchten. Und wie! Ersten Kontakt mit der buntgemischten Gruppe aus Berlin hatte ich auf einem Konzert der Söhne Mannheims. Dort traten sie als Vorgruppe auf und... Wow, man, they kick ass! Sobald man die ersten Takte hört bzw. spürt, will sich der Körper einfach nur bewegen. Man kann eigentlich nichts dagegen machen, einfach loslassen, tanzen und wohlfühlen - richtig genial! Die Mucke ist Programm. Geniesse das Leben in vollen Zügen - Bamboocha eben. Wie soll man es beschreiben? Jamaica-Style meets Hiphop meets Funpunk? Joah, das könnte es ziemlich treffen. Nach dem Erstalbum 'Union Verdadera' kommt ebenfalls am Montag das neue Album 'Next Generation' auf den Markt. Leute, geniesst das Leben und zieh' es euch rein. Boy, ich bin echt tierisch überrascht, welche Stimmungswechsel Musik hervorrufen kann. Wenn man die letzten Blogeinträeg betrachtet, dann haben wir hier die 180° Drehung. Musik macht Stimmung! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'die-happy-a-culcha-candela.html'; this.page.identifier = '9271ed1c97_iddie-happy-a-culcha-candela'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "die-jungs-bei-google-haben-spass.html": {
    "href": "die-jungs-bei-google-haben-spass.html",
    "title": "Die Jungs bei Google haben Spass... - Get Blogged by JoKi",
    "summary": "Die Jungs bei Google haben Spass... Copy Markdown View Markdown GitHub Markdown Source Die Jungs bei Google haben Spass... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-jungs-bei-google-haben-spass'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-jungs-bei-google-haben-spass'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Die Jungs bei Google haben Spass...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-17 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Kam gerade per Mail bei mir rein: Anleitung: Geh auf https://www.google.de Gib \"Failure\" ein. (auf deutsch: \"Versagen\" 2b. Filter auf \"Das Web\" einstellen (falls nicht Standard) Klick auf \"Auf gut Glück!\" (Ist der Button neben suchen Lach dich kaputt. Verbreite diese Anweisung, bevor die Google-Leute das ändern müssen. Falls das Ergebnis nicht mehr passen sollte... Hier der direkte Zugang Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'die-jungs-bei-google-haben-spass.html'; this.page.identifier = '9271ed1c97_iddie-jungs-bei-google-haben-spass'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "die-rckkehr-des-knigs.html": {
    "href": "die-rckkehr-des-knigs.html",
    "title": "Die Rückkehr des Königs - Get Blogged by JoKi",
    "summary": "Die Rückkehr des Königs Copy Markdown View Markdown GitHub Markdown Source Die Rückkehr des Königs Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-rckkehr-des-knigs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-rckkehr-des-knigs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Die Rückkehr des Königs') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-10-17 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Yeah, finally. Endlich ist die Extended Edition des dritten Teils von Der Herr der Ringe - Die Rückkehr des Königs - bestellbar. Amazon hat Anfang letzter Woche die virtuellen Shoppingtüren weiter geöffnet. Eigentlich selbst verständlich, daß die Box sofort bestellt wird. Wobei ja inzwischen der Untertitel Collector's Edition verstärkt Augenwischerei ist - oder sollte man eine Ausgabe mit 500.000+ Exemplaren wirklich noch als Sammleredition bezeichnen? Nunja, nicht mein Problem - das wird's erst, wenn das Paket nicht ankommen sollte... Apropos Amazon - gleich mal was in egoistischer Sache - ich hab' einen Wunschzettel eingerichtet... Da meine aktuellen Entwicklung zu Active FoxPro Pages, sowie derzeit alle Artikel online kostenfrei an die Community gehen, gönne ich mir den Luxus meinen Träumen einen kommerziellen Rahmen zu verpassen und anderen eine Möglichkeit an die Hand zu geben, sich eventuell erkenntlich zu zeigen - nur keine Angst, ich erwarte nichts und freue mich garantiert umso mehr, wenn es tatsächlich klappen sollte... \uD83D\uDE01 Auf geht's Weihnachten steht vor der Tür... Gibt's eigentlich schon Lebkuchen bei Aldi? grübel Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'die-rckkehr-des-knigs.html'; this.page.identifier = '9271ed1c97_iddie-rckkehr-des-knigs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "die-zukunft-von-vfp.html": {
    "href": "die-zukunft-von-vfp.html",
    "title": "Die Zukunft von VFP - Get Blogged by JoKi",
    "summary": "Die Zukunft von VFP Copy Markdown View Markdown GitHub Markdown Source Die Zukunft von VFP Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-zukunft-von-vfp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'die-zukunft-von-vfp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Die Zukunft von VFP') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-06-13 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Mahlzeit, hier 'ne Kleinigkeit zu lesen \uD83D\uDE09 https://msdn.microsoft.com/vfoxpro/roadmap/ https://msdn.microsoft.com/vfoxpro/letters/ https://www.craigberntson.com/archives/2005_06_01_archive.asp#111764598359410031 Tja, dafür, dass er alles offenlegen will, ist die Roadmap wirklich sehr dünn. Im Universalthread ist Ken etwas offener. Hier ein paar Auschnitte aus Messages von Ken von gestern und heute im UT: Thanks for the feedback. I just did a video interview in my office by Robert Scoble for Channel 9 showing some new features we are planning to include in Sedna and demos of VFP working with .NET and VS 2005, as well as Avalon (XAML). It is all a very early sneak preview of the type of things we are working on. And although it is an early start, the demos are pretty exciting. I'll have this video go online in mid June for anyone to watch (including non-VFP developers). And while it is a start, it is only the beginning since we just started on Sedna and won't go full speed on it until July. Some of the demos relate to new IntelliSense features, new report system features beyond what is in VFP 9.0, and lots of cool interop with .NET. Stay tuned. \uD83D\uDE03 Exactly, as .NET becomes the primary Microsoft platform for application development long term. It is much like creating a Windows version of FoxPro last decade, going from DOS to Windows. We are working on things to help VFP developers, businesses using VFP applications, etc. for the long term (second half of this decade and into the next). Our focus and resources are going into enhancing VFP for interoperabiilty, not the core VFP product beyond what is needed to enhance and deliver on the interop story. Leveraging the .NET Framework, features for running VFP apps on Longhorn, being able to call Avalon forms from VFP, working with SQL Server and Office better, etc. are all a higher priority than adding new features to the grid and other controls, etc. We will enhance many of the IDE features in VFP via extensiblity. Lots can be done here, more than some might expect or imagine. That includes new DLLs written in C++ and not just Xbase (FoxPro) code. Yeah, and the time and effort (costs) of adding IDE things to VFP is not as important has how VFP apps deploy (the end user experience). The runtime (end result) is what is more important than nice features. Keep in mind that the VFP team now (and with VFP 9.0) is under 10 people while people working on VS 2005 and the .NET Framework is well over 1000. Ich schließe mich weitgehend der Meinung von Craig Berntson an. Die Road zu .net ist für mich, zumindest aus heutiger Sicht, kein Weg. Mal sehen, ob Ken mich von etwas anderem überzeugen kann. \uD83D\uDE03 Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'die-zukunft-von-vfp.html'; this.page.identifier = '9271ed1c97_iddie-zukunft-von-vfp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "diy-windows-game-box.html": {
    "href": "diy-windows-game-box.html",
    "title": "DIY Windows Game Box (Steam, Itch, Xbox, Store, Scumm, etc.) - Get Blogged by JoKi",
    "summary": "DIY Windows Game Box (Steam, Itch, Xbox, Store, Scumm, etc.) Copy Markdown View Markdown GitHub Markdown Source DIY Windows Game Box (Steam, Itch, Xbox, Store, Scumm, etc.) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'diy-windows-game-box'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'diy-windows-game-box'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'DIY Windows Game Box (Steam, Itch, Xbox, Store, Scumm, etc.)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-10-17 - Filed under WindowsProjects (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Working in IT has its up's and down's. One of the up's is probably the circumstance of purchasing new hardware on a regular base. (Un)fortunately two events came together this year - an aging machine and tax depreciation purpose - which encouraged me to purchase a new rig for software development. As for the aging machine, an MSI GS70 2PE Stealth Pro, I had a few issues cropping up lately, and so I decided to get in touch with the original supplier to discuss possibilities to refurbish the system. My main sorrow was a partially broken display connector cable, meaning I couldn't open the display beyond a certain angle anymore. Otherwise, the screen would either freeze or would show stripe patterns from a different world. While this would be manageable by using an external screen, two actually, there were other glitches in the matrix... Long story short, after I received my new machine, an MSI GS73VR 7RF Stealth Pro, that old system went back to the supplier for general cleaning and check up. Luckily, it didn't take that long to get it back... Now, what to do with it? Installing latest Windows 10 Insider Having a spare system with such a powerful specification it was somehow obvious to me to use it as a bare metal playground for the latest Windows 10 Insider editions. And with the intention to not use the system for any work-related tasks I thought that it would probably be an interesting experiment to convert it into a full-fledged gaming machine. And I mean not that kind of emulator stuff only but recent titles and so on. Previously, I was using mainly VMs to explore Windows 10 Insider versions, and now using a dedicated bare metal system would surely improve my experience in that area. If you're interested in setting up a new system, please read on here: How to get your computer from blank disk(s) to fully operational in no time... (Windows edition) The nice side-effect of using a Windows 10 Insider version is that it comes for free, too. And actually, I like the thrill to get the latest features and the ability to provide feedback - positive and negative - to Microsoft. It's kind of fun... Steam - Big Picture mode On a common Windows system you might like to install and to use Steam as a regular software application. Perhaps accept the auto-start feature after logging in, and letting it run in the background. As stated earlier, not for that machine... Its main purpose is getting the maximum out of it for gaming at home. After installation of the latest Steam version (via chocolatey: steam) I opened the File menu and checked under Settings > Account > Beta participation whether a vNext might be available. Tada! The option to opt-in into the Steam beta program is given. Let's do it! After quick download and restart of Steam software we are all set. Now, the system is ready for the big screen and therefore needs to launch into the Big Picture mode of Steam automatically. Open the File menu and navigate to Settings > Interface in order to tick the two checkboxes - Run Steam when my computer starts & Start Steam in Big Picture Mode, as shown in the screenshot below. HDMI and a bit of cabling After installation of Windows 10 and adjusting Steam properly, it is time to pay a little attention to further hardware. The GE70 has three video outputs - one HDMI and two mini-DisplayPort. To keep things simple I got a 2 meter-long HDMI 1.4 cable from the electronics shop around the corner to connect the laptop to the TV. The cable is long enough to allow comfortable placement of the computer under the TV and provides enough length for flexible handling. I directly bought an HDMI cable with version 1.4 specification which in case of a future upgrade of the TV to 4K resolution would still be sufficient. Oh yes, thanks to the NVidia GeForce GTX 870M graphics card the laptop has enough resources for such a configuration. Time for a test run... Having everything in place it is finally time to give the whole system a test run. I shutdown the machine completely, and then let it boot again. After entering login pin for my account it takes a short moment and Steam starts automatically into Big Picture Mode on the TV screen. Well, that went very well for a first attempt. Launching an installed Steam game works as expected and it appears nicely on the TV, too. Let the games begin... Itch.io - Marketplace for independent game creators Next, I installed the desktop version of Itch.io and went through my library of games. One of my current titles I absolutely adore is \"Return of the Tentacle - Prologue\" by game developer catmic - a fan project and the unofficial sequel to the iconic adventure game \"Day of the Tentacle\". It is a good old 2D point and click adventure game developed on Unity and has astonishing graphics and superb soundtrack. Highly recommended for any gamer that misses titles like Sam & Max, Zak McKracken and the Alien Mindbenders, or even the Monkey Island series. Again, no surprises and the game launches nicely into full screen mode on the TV set. Splendid! Let's explore the Microsoft Store Of course, it's interesting to check out the Microsoft Store for any kind of games. Your mileage may vary... For testing purpose I installed Asphalt 9: Legends. Although the download took a while to complete, launching and playing the game on the TV was a breeze. It's actually good fun to drift around on the big screen, and the sound from the TV is amazing compared to the laptop speakers. Xbox app I would say it works, too. As of writing this article I did not venture that much into the application but so far I did not run into any surprises nor issues. Perhaps I have to give it a bit more time... We will see. ScummVM Sorry folks... Haven't had the time to install ScummVM yet. Totally bummed about that, and one of the best 2D game platforms on the planet. It's definitely on my roadmap, and I'll keep you posted as soon as it's done. Optional hardware (recommendations) Using a spare machine like I did here and converting it into an experimental DIY gaming box does not seem to be that difficult after all. Of course, the real feel of gaming is related to additional hardware, mainly gamepads and a remotely connected keyboard and mouse. For the gamepads you can literally take anything that hooks up nicely with Windows 10, e.g. Xbox or PlayStation game controllers. I managed to pair one of my OUYAGame Controllers via Bluetooth, and also plugged in an older Thrustmaster gaming pad via USB. After an initial calibration they both work nicely. Although I had to set up a controller profile on Steam to map the various buttons and axis correctly. Next stage: Video calls The next chapter I going to explore is all about instant messengers and their video call capabilities. I'm aware that the laptop has built-in video camera and microphone... The quality is also good for regular desktop use, meaning close proximity. The usual suspects, e.g. Skype, Messenger, Signal, etc. should work nicely in regards to display the call partner on the TV but before trying it out I would like to purchase a better HD camera with wide-field microphone and then connect it via USB to the system. Guess that this is going to be good fun to have family conversations... Future experiment: Live Streaming Yeah, this might sound a bit crazy, who knows but having all the gaming applications in operation I'm actually interested to see whether it will be possible to get live streaming on Steam, Xbox Live, Twitch or perhaps even YouTube done. Surely, there is more work to be done and proper video and audio equipment will be essential for this adventure. I'm curious about it. And I'm looking forward to write another blog about that... Give your 'older' hardware a chance Computer systems are quite powerful since a few years already. One might say that they are over-dimensioned for common use, like browsing the web, taking care of emails and writing letters, etc. Given the hardware specs of my previous machine I gave it try to convert it into a decent gaming 'DIY console' - successfully, IMHO. Maybe you have a spare system lying around on your desk, and perhaps you don't know what to do with it. Hopefully, this article gave you a little bit of an inspiration. And, in case of lower specs you may look into alternative approaches. There are some great arcade emulators around that will offer same or even better fun than Steam & Co. Have a look at Lakka - The open source game console... What about you? Did you set up something similar already? If yes, what did you do? Did you run into any obstacles or speed bumps? Do you have any recommendations to look into? I'm really interested to hear about your DIY game console in the comment section below. Thanks and happy gaming! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'diy-windows-game-box.html'; this.page.identifier = '9271ed1c97_iddiy-windows-game-box'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "driving-business-forward.html": {
    "href": "driving-business-forward.html",
    "title": "Driving Business Forward - A new approach to social media in Mauritius - Get Blogged by JoKi",
    "summary": "Driving Business Forward - A new approach to social media in Mauritius Copy Markdown View Markdown GitHub Markdown Source Driving Business Forward - A new approach to social media in Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'driving-business-forward'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'driving-business-forward'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Driving Business Forward - A new approach to social media in Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-01-28 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Image is courtesy of Knowledge7. There are new and fresh activities going on in Mauritius. Thanks to the effort of Knowledge 7, I could attend the first meetup of an upcoming series of meetings on technology and social media. The three sessions were run under the umbrella topic of \"Driving business forward with Social Networks, Mobile & Cloud technologies\" and covered the importance of smartphone platform, an innovative way to leverage Oracle Application Development Framework (ADF) for a local business in the field of construction and last but not least on how to choose and use Social Media platforms like Facebook, LinkedIn, Google+ or Twitter to embrace and communicate with your consumers. Overall I was very pleased by the level of professionalims in the organisation prior to the event and the actual execution on the premises of the University of Mauritius. The audience was also very diverse and various types of people were present: students, SEO freelancer, ghost writer, marketing manager of local and international brands, and even deligates from local newspaper and magazines. The Q&A session at the end of the meeting gave me a nice overview of other's struggles and success stories as well as new ideas for my businesses. A big Thank You to the team at Knowledge 7 and the speakers. I'm really looking forward to one of the next gatherings. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'driving-business-forward.html'; this.page.identifier = '9271ed1c97_iddriving-business-forward'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "dumm-gebabbelt-iss-glei.html": {
    "href": "dumm-gebabbelt-iss-glei.html",
    "title": "Dumm gebabbelt iss glei! - Get Blogged by JoKi",
    "summary": "Dumm gebabbelt iss glei! Copy Markdown View Markdown GitHub Markdown Source Dumm gebabbelt iss glei! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dumm-gebabbelt-iss-glei'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dumm-gebabbelt-iss-glei'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Dumm gebabbelt iss glei!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-07 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Oder wie es ein bekannter Politiker A.D. mal formulierte: Spass muss sein, Spass muss sein... Gerade mal einen Blick auf die geistigen Ergüsse meiner geschätzten Community-Animals - Tiger & Eisbär - geworfen und ein wenig kommentiert. myCSHARP.de Wallpaper Nullable value types Mal auf Feedback gespannt... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'dumm-gebabbelt-iss-glei.html'; this.page.identifier = '9271ed1c97_iddumm-gebabbelt-iss-glei'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "dynamic-link-libraries-in-vfp-verwenden.html": {
    "href": "dynamic-link-libraries-in-vfp-verwenden.html",
    "title": "Dynamic-Link Libraries in VFP verwenden - Get Blogged by JoKi",
    "summary": "Dynamic-Link Libraries in VFP verwenden Copy Markdown View Markdown GitHub Markdown Source Dynamic-Link Libraries in VFP verwenden Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dynamic-link-libraries-in-vfp-verwenden'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'dynamic-link-libraries-in-vfp-verwenden'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Dynamic-Link Libraries in VFP verwenden') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-19 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich glaube, dass sich heute (und die letzten beiden Tage) ein paar Stammtischteilnehmer vielleicht wie am 1. April vorkamen. Tut mir leid, alles meine Schuld, ich hab's verbockt. Laut der Website zum Stammtisch - https://speyer.dfpug.de - und meiner Erinnerungsmail sollte sich der Abend mit dem Internet Explorer in VFP-Anwendungen beschäftigen. Nun, sollte... irgendwie habe ich es verpeilt, dass Jörg bereits im März mir zur Kenntnis gab, dass er diese Woche in Urlaub ist, und daher nicht zur Verfügung steht. Gut, alternativ hätte man dann Automation von Microsoft Winword für Druckausgaben anstreben können. Aber Moment mal, da wäre Jörg ebenfalls als Redner aktiv gewesen. Herrje, wieder nichts, okay, dann eben Plan B und das zweite Thema vom März - Dynamic-Link Libraries in VFP verwenden - nachholen. Ihr könnt euch vielleicht vorstellen, dass wir hiermit den zweiten Satz der Thermodynamik ziemlich gut erfüllt haben. Nun, zumindest stand der Termin; wie bisher immer am 3. Mittwoch des Monats. Pünktlich um 19:30 durften wir uns im frisch renovierten (großen) Nebenraum unseres Lokals versammeln und loslegen. Zunächst wie üblich die Neuigkeiten aus der Community: Usergroupunterstützung durch O'Reilly und MS Press Infos von CLIP und der INETA Ausblick auf die Regionalleitertreffen von PASS und dFPUG Ein paar Nachzügler trafen noch ein und dann konnten wir auch direkt mit dem Thema des heutigen Abends loslegen: Dynamic-Link Libraries (DLLs). Laut MSDN sind DLLs 'lediglich' Module mit ausgelagerten Funktionen und Daten, welche von anderen DLLs oder Anwendungen genutzt werden können. Klingt im ersten Moment sehr stark nach Prozedurenbibliotheken und APP-Projekten in Visual FoxPro, und im zweiten Augenblick hat sich auch kaum etwas geändert. Einzig der Umstand, dass DLLs in zwei verschiedenen Ausprägungen vorliegen können. Ich beleuchte Dynamic-Link Libraries hier nur im Zusammenspiel mit den Eigenheiten von VFP und nicht allgemein für andere Programmiersprachen. In VFP können DLLs über zwei unterschiedliche Wege genutzt werden: Declare Befehl CreateObject(), NewObject() oder GetObject() Instanzierung über COM Declare Über den Declare-Befehl können wir direkt auf die exportierten Funktionen einer Bibliothek zugreifen und diese in den eigenen Speicher laden. Das Ganze passiert dann In-Process und die deklarierten Funktionen stehen dem Entwickler wie normale VFP-Funktionen zur Verfügung. Man kann sich mittels des Befehls DISPLAY DLLS über die aktuell zur Verfügung stehenden Funktionen informieren. Hier mal ein simples Beispiel: ` Declare Integer Sleep In kernel32.dll ; Integer Sleep(4000) && 4 Sekunden warten Clear Dlls \"Sleep\" ` Wir laden uns die exportierte Funktion Sleep aus der kernel32.dll in unsere Anwendung, führen sie dann mit einem Parameter (gemäß Deklaration) aus, und entfernen sie abschließend wieder. Das gleiche Verhalten wie beim Aufruf einer Funktion in einer VFP-Prozedurendatei, welche mittels Set Procedure To referenziert wurde. Der Verweis auf die DLL erfolgt normalerweise ohne Pfadangaben, kann aber auch Pfadinformationen nutzen. Ohne Pfad sucht VFP die DLL an den üblichen Stellen im Betriebssystem und verweigert erst die Ausführung, falls wirklich nichts zu finden ist. Das kann potentiell zu Problemen führen, falls unterschiedliche Versionen der gleichen DLL vorliegen. Hier gilt es dann mittels DISPLAY DLLS zu prüfen, welche physikalische Datei herangezogen wurde. COM-Schnittstelle Die zweite Art der Nutzung von Dynamic-Link Libraries in VFP kann über die COM-Schnittstelle erfolgen. Hier gelten dann de facto die gleichen Regeln wie der OLE Automation. Objektreferenz auf die Klassendefinition erzeugen, Methoden mit passenden Parameter ausführen und die Rückgabewerte interpretieren. Im letzten Beitrag .NET per COM nutzen habe ich genau diesen Fall bereits ausführlicher beschrieben. Hilfsmittel erleichtern die Arbeit Oftmals steht man am Anfang vor dem Problem überhaupt Informationen zur vorliegenden DLL zu erhalten. Denn entweder kennt man die Funktionen einer Bibliothek überhaupt nicht, oder es liegen zu wenige Informationen oder Dokumentation vor. Meine gegenwärtige Vorgehensweise ist die, dass ich zuerst die DLL im Dependency Walker öffne und nachschaue, ob exportierte Funktionen vorliegen. Falls ja, okay, dann kann ich mit Declare arbeiten; falls nein, dann weiß ich, dass ich die DLL registrieren muss und über die COM-Schnittstelle zu gehen habe. Danach nutze ich die Fähigkeiten des Object Browser in VFP und schaue, welche Klassen samt Methoden, Eigenschaften und Ereignissen (und Interfaces) zur Verfügung stehen. Sollten die ersten Tests mittels Createobject im Command Window fehlschlagen, gint's einen kurzen Exkurs mit Suchabfrage in der Windows-Registry, um die ProgID des COM-Servers zu ermitteln. Danach lässt sich in den meisten Fällen stressfrei mit der DLL in VFP arbeiten. Parametertypen und Strukturen Im Umgang mit den Funktionen bzw. Methoden einer Dynamic-Link Library wird man öfters (eigentlich immer) vor das Problem gestellt, dass man bestimmte Typen für die Parameter anzugeben hat. Die üblichen Vertreter sind hierbei Handle (Integer), LptStr (String) und dann die fieseren Strukturen. In der Onlinehilfe von VFP findet man eine kleine Übersetzungstabelle für die üblichen C/C++ Datentypen auf die gewohnten VFP-Typen. Diese solltet ihr euch ausdrucken und in der Nähe des Computers zurecht legen. Bei Strukturen hilft meistens nur der Taschenrechner. Denn Strukturen werden in VFP als String mit der entsprechenden Summe der Länge der genutzten Datentypen genutzt. Alles klar... oder? In den Solution Samples gibt es dazu übrigens ein Beispiel. Was tun bei Fehlern? Zuerst werfe ich bei Fehlern im Zusammenhang mit DLLs je nach Typ entweder den Dependency Walker oder den VFP Object Browser an, um mich zu vergewissern, dass meine Funktionsaufrufe die richtigen Parametertypen verwenden. Danach nehme ich den FileMon von Sysinternals um zu prüfen, ob eventuell Pfadangaben fehlerhaft sind oder ob benötigte Dateien nicht gefunden werden. Hierbei ist ein bisschen Detektivarbeit gefordert, aber man hat sehr hohe Trefferquoten zur Behebung der Fehlerursache. Es soll auch ab und zu hilfreich sein, mal in die Dokumentation zur DLL reinzuschauen (sofern vorhanden). Und falls die DLL von einem Drittanbieter stammt, kann man immer noch eine Mail versenden und einfach mal fragen. Weitere Informationen und Beispiele Zunächst sollte man in VFP die Informationen zum Declare-Befehl und den referenzierten Befehlen und Artikel durchlesen. Danach bietet sich auf alle Fälle die MSDN Bibliothek an, da in den meisten Fällen mit einer der hunderten Funktionen der Windows API gearbeitet wird. Und bei Problemfällen mal die genannten Tools probieren. Hier die Links in der Übersicht: https://msdn.com bzw. https://msdn.microsoft.com https://www.dependencywalker.com https://www.sysinternals.com https://jochen.kirstaetter.name/files/joki_DynamicLinkLibraries.zip (Slide und Code) /NET-per-COM-nutzen /Spass-mit-der-MessageBox Der nächste Termin für den Stammtisch ist am 17. Mai 2006 und das Thema wird wahrscheinlich nicht AFP sein, sondern wird mittelfristig per Mail und Forum noch bekannt gegeben werden. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'dynamic-link-libraries-in-vfp-verwenden.html'; this.page.identifier = '9271ed1c97_iddynamic-link-libraries-in-vfp-verwenden'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "effiziente-anwendungsentwicklung-in-vfp.html": {
    "href": "effiziente-anwendungsentwicklung-in-vfp.html",
    "title": "Effiziente Anwendungsentwicklung in VFP - Get Blogged by JoKi",
    "summary": "Effiziente Anwendungsentwicklung in VFP Copy Markdown View Markdown GitHub Markdown Source Effiziente Anwendungsentwicklung in VFP Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'effiziente-anwendungsentwicklung-in-vfp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'effiziente-anwendungsentwicklung-in-vfp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Effiziente Anwendungsentwicklung in VFP') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-29 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Anwendungsentwicklung, Softwaredesign, Design, Visual FoxPro Den heutigen Tag habe ich für die Korrekturlesung des deutschsprachigen Buches \"Effiziente Anwendungsentwicklung in VFP\" aus dem Verlag Hentzenwerke genutzt. Eigentlich sollte der Titel bereits Anfang des Jahres erschienen sein, aber die gegenwärtige Fassung bedarf noch einiger Anpassungen. Es handelt sich bei diesem Buch übrigens um die Übersetzung und Aktualisierung des englischen Titels \"Effective Techniques for Application Development with Visual FoxPro\" für Visual FoxPro 9.0. Mit der stetigen Weiterentwicklung von VFP und neuen Features wie etwa aktualisierbare Cursor, CursorAdapter-Klasse und die ganzen XML-Fähigkeiten ergeben sich natürlich auch neue Bewertungen für die effiziente Anwendungsentwicklung. Das Buch umfasst derzeit 14 Kapitel und vier Anhänge verteilt auf über 370 Seiten. Die deutschsprachige FoxPro User Group (dFPUG) hat sich für die Übersetzung und Aktualisierung des Buchtitels stark gemacht, da insbesondere in den Foren und in den Newsgruppen zu Visual FoxPro ständig wiederholend Fragen und Problemstellungen zur besseren Nutzung von VFP auftreten. Leider entsteht hier der Eindruck, dass sehr viele Entwickler unterhalb der 50% Leistungsfähigkeit von VFP arbeiten und damit viele Vorzüge und Leistungen ihrer Programmiersprache und Datenbank eben verschenken und darüberhinaus wertvolle Zeit durch die Entwicklung paralleler Funktionalitäten vergeuden. Ich muss gestehen, dass ich durch die Arbeit mit dem Buch, den leichten Umformulierungen und der Prüfung der vorliegenden Codebeispiele selbst einen sehr guten Einblick in die Funktionsweise und Nutzung von Ansichten erhalten habe. In meinen bisherigen sieben Jahren Entwicklung unter Visual FoxPro habe ich ausschliesslich mit selbsterzeugten Cursorn und SQL PassThrough (SPT) gearbeitet. Dies liegt aber auch daran, dass wir eine eigene Datenbankabstraktionsschicht in sämtlichen Projekten einsetzen und damit einen objektorientierten Zugriff auf die Datenquellen besitzen. Dennoch bieten Views (zu deutsch Ansichten) unabhängig davon viele Vorzüge gegenüber dem direkten Zugriff auf lokale Tabellen und der alleinstehenden Arbeit mit SPT. Die CursorAdapter-Klasse erweitert diese Funktionalität sogar noch weiter, in der Hinsicht, dass eine bessere Kontrolle über die Datenmanipulation der zugrundeliegenden Datenquelle möglich ist. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'effiziente-anwendungsentwicklung-in-vfp.html'; this.page.identifier = '9271ed1c97_ideffiziente-anwendungsentwicklung-in-vfp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ego-search-und-die-treffer.html": {
    "href": "ego-search-und-die-treffer.html",
    "title": "Ego-Search und die Treffer - Get Blogged by JoKi",
    "summary": "Ego-Search und die Treffer Copy Markdown View Markdown GitHub Markdown Source Ego-Search und die Treffer Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ego-search-und-die-treffer'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ego-search-und-die-treffer'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ego-Search und die Treffer') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-21 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ausgehend vom vorherigen Blogeintrag und der regen Unterhaltung mit Thomas habe ich mal wieder eine 'Ego-Search' in Betracht gezogen. Dabei geht's einfach mal darum zu sehen, welche Treffer in den Suchmaschinen gelistet werden, wenn man seinen Namen oder nur den Familiennamen eingibt. Gut, bei Google bin ich mit acht von zehn unter den ersten Treffern aktuell #1. Aber wesentlich interessanter finde ich die weiteren Treffer: Kirstätter & Partner Massivhaus GmbH - Mosbach-Neckarelz Brancheneintrag für Beleuchtung - Mannheim Alfred Kirstätter - Karlsruhe Friedrich Kirstätter Elektrotechnik - Kleinsendelbach Verstorbener Jazz-Musiker: Kiki Kirstätter - Mannheim Nadja Kirstätter - Sersheim Tamara Kirstätter - Neuwied (nicht meine Schwester \uD83E\uDD2A) Lehrer(in) Kirstätter - Karlsruhe Reno GEDCOM - Genealogie mit mehreren Verweisen Einwanderer in die Neue Welt - Genealogie mit mehreren Verweisen Rainer Kirstätter - Psychologe, Körperpsychotherapeut (HP) in freier Praxis, Gruppentrainer. Klaus Kirstätter - Geschäftsführer Cartec GmbH Dozent Institut für Soziologie, Medien- und Kulturwissenschaft - Karlsruhe Andrea Kirstätter - Pflegerischer Beratungsdienst, Linkenheim-Hochstetten [url=]Marion Kirstätter - Beamtin Rathaus Speyer (Abt. 410 - Sozialhilfe und Sozialleistungen) Ron Kirstatter - USMC The Kirstatters - Ron's family wie es scheint Alumni Abgänger 1990 - Lita Marie und Ronald V. Kirstatter - Hm, Ronald und Lita könnten zusammengehören. Zumindest lassen andere Ressourcen darauf schliessen. Stefan Kirstätter Ist schon ziemlich interessant wie weit verbreitet dieser eher unübliche Name in der Welt vertreten ist. Stellt sich hierbei natürlich auch die Frage wie es mit der Genealogie insgesamt aussieht. Leider stellt sich aktuell heraus, dass meine Präsenz in Suchmaschinen derzeit die höchste Trefferquote aufweist. Ich hoffe, dass ich hier vielleicht ein wenig beitragen kann. Potentiell würde es mich ja auch reizen, mal wirklich hinter die Kulissen zu schauen. In Anbetracht der Verstreuung des Namens sicherlich interessant und spannend. Nunja, es ist ja nur ein Familienstrang auf den ich zurückgreifen kann, schliesslich habe ich ja eine andere Vergangenheit... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ego-search-und-die-treffer.html'; this.page.identifier = '9271ed1c97_idego-search-und-die-treffer'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ein-knigreich-fr-eine-bernachtung.html": {
    "href": "ein-knigreich-fr-eine-bernachtung.html",
    "title": "Ein Königreich für eine Übernachtung - Get Blogged by JoKi",
    "summary": "Ein Königreich für eine Übernachtung Copy Markdown View Markdown GitHub Markdown Source Ein Königreich für eine Übernachtung Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ein-knigreich-fr-eine-bernachtung'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ein-knigreich-fr-eine-bernachtung'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ein Königreich für eine Übernachtung') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-08 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Kein Zimmer mehr frei... So ging es mir von Donnerstag auf Freitag. Auf Grund einer nicht abgesprochenen Annahme war nicht ganz klar, wieviele Nächte für den Workshop gebucht werden sollten. Nunja, und da kam dann noch Murphy's Law mit ins Spiel. Denn selbst eine Verlängerung des Aufenthalts im Hotel war nicht machbar, da komplett ausgebucht. Hm, okay, in der Nacht noch einen Hilfeaufruf per Mail an die Kollegen zuhause abgeschickt und mal abwarten was passiert. Starten wir den Tag mit Frühstück und dann in den Endspurt des AFP Workshops. Heute wenden wir das Wissen der bisherigen zwei Tage konkret in einer Prototypanwendung an. Wir wollen eine Kombination aus Code Review, Refactoring und Vermittlung von Best Practices umsetzen. Ganz im Sinne des Kunden, der seine AFP Webanwendung sauber und leicht implementieren möchte. Hier gilt es auch Augenmerk auf die Erweiterbarkeit und Wartbarkeit zu legen. Murphy's Law Oder wieso funktionieren die Internet Information Services auf einmal nicht mehr? Zur Sichtung des Prototypen erstellen wir ein neues Entwicklungsverzeichnis, konfigurieren eine neue virtuelle Site im IIS und wollen den ersten Aufruf der Anwendung durchführen. Könnte ja sein, dass noch weitere Einstellungen vorzunehmen sind. Nun, dann ging's los... HTML und Grafiken werden aufgerufen, aber keine AFP-Dokumente. Nun, im ControlCenter wird ersichtlich, dass die Requests vom IIS nicht in der AFP ankommen. Okay, Prüfung der Eigenschaften, kein Erfolg... hm, dann das bewährte Hausmittel bei Windows: Reboot Und ja, nach dieser kurzen Zwangspause sind wir wieder im Boot und können weiterarbeiten. Na also, sieht soweit schon mal gut aus. Und von den Teilnehmern kommen auch die ersten Anregungen und Ideen zur Veränderung. Okay, dann mal die Arme hochkrempeln und die Applikation zerlegt. Dazu splitten wir die vorhandenen AFP-Dokumente in ihre Bestandteile: HTML-Vorlagen und -Bausteine, CSS-Formatvorlagen, JavaScript, AFP-Anwendung und CodeBehind zur Auslagerung der genutzten Funktionalität. Einiges wandeln wir bereits in Klassen um. Und so bringen wir auch noch die restlichen Stunden des Tages unter den Hut. Übernachtung gesichert Puuh, keine Notwendigkeit die Bahnhofsmission zu beehren. Und besser konnte es nicht werden. Direkt ein Hotel am Flughafen. Also, immerhin die Möglichkeit ein wenig auszuschlafen, zu frühstücken und dann frisch und erholt nach Prag fliegen. Nur... welches Hotel und welcher Flughafen. Es ist nicht gerade hilfreich, wenn Buchungsadresse und Internetinformationen zum Hotel voneinander abweichen und der Lageplan ebenfalls nicht passt. Nunja, erstmal zum Berliner Hbf, von dort aus mal weiterschauen... Nach einem kurzen Anruf beim Hotel war die Lage geklärt und die Anfahrt gesichert. Gut, nichts wie hin... Relaxen und evtl. abends wieder in die Innenstadt. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ein-knigreich-fr-eine-bernachtung.html'; this.page.identifier = '9271ed1c97_idein-knigreich-fr-eine-bernachtung'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ein-tag-hat-24-stunden.html": {
    "href": "ein-tag-hat-24-stunden.html",
    "title": "Ein Tag hat 24 Stunden - Get Blogged by JoKi",
    "summary": "Ein Tag hat 24 Stunden Copy Markdown View Markdown GitHub Markdown Source Ein Tag hat 24 Stunden Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ein-tag-hat-24-stunden'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ein-tag-hat-24-stunden'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ein Tag hat 24 Stunden') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-01-28 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nicht mehr und auch nicht weniger. Zwar handelt es sich dabei nicht um einen exakten Wert, aber die jährliche Streuung ist dennoch relativ gering. Was auch ganz okay ist... Es bleibt ein wenig Zeit zum Arbeiten, zum Essen, Relaxen und Schlafen. So zumindest die einhellige und gängige Meinung bzw. der Alltag eines jeden. Sicherlich gibt es ab und zu auch Abweichungen von dieser Regelmäßigkeit. So auch der gestrige Tag. Inzwischen konnte ich mir ein paar Stündchen Schlaf gönnen, dennoch bleibt der Hochgenuß des gestrigen CLIP Community-Treffen in Düsseldorf erhalten. Es hat richtig Spass gemacht! Erstaunlicherweise habe ich es sogar überpünktlich und auf direktem Wege zum Treffpunkt des Tages geschafft. Hm, ein wenig Orientierungssinn im Großstadtdschungel bewirkt schon Wunder. Zusammen mit Viviana, Tom und Nail ging's dann auch direkt ins Bierhaus. Hahaa - wir sind bestimmt die ersten und es wird noch keiner da sein... Pustekuchen! Mathias - ebenfalls dFPUG Regionalleiter (in Essen) und seines Zeichen Fuchsbändiger - saß bereits munter telefonierend in der 'Sauna' und begrüßte uns. Yes, wenigstens nicht alleiniger Exot des Tages. \uD83D\uDE01 Nach und nach füllte sich der Raum mit CLIP-Mitgliedern. Einige Gesichter kannte ich bereits von den vorherigen GetTogethers, aber es waren erfreulicherweise auch neue Personen von der Party. Zunächst gab's von Viviana eine kurze Agenda für die weitere Vorgehensweise und dann ging's auch direkt ans Eingemachte. Jeder stellte sich und seine Community nochmal kurz vor. Dabei wurde die Stimmung auch schon lockerer und unverkrampfter. Achja, es gab zu diesem Zeitpunkt nur Kaffee, Wasser und Apfelsaft. Nicht, dass jemand annimmt, wir hätten den offiziellen Teil des Treffens im Gelage abgehalten. Nee, nee, ganz im Gegenteil - die reinste Folter. \uD83E\uDD2A Leider vergingen die angesetzten drei Stunden viel zu schnell. Nunja, wir haben ja später noch Zeit für weiteres Networking. Auf geht's zur Brauhausführung... Wirklich spannend, wie man auf dem begrenzten Platzangebot dennoch soviel Maschinerie und Personal unterbringen kann. Das komplette Haus - also vom Keller bis unter den Dachgiebel ist die Brauerei. Absolut spannend. Nun, ich kenne die Führung in der Privatbrauerei Bischoff, aber das Uerige ist einige Nummern kleiner. Nach einer zünftigen Verkostung in den Abendstunden gab's reichlich Raum un dZeit für Gespräche unter uns CLIP-Mitgliedern. Es ist immer spannend mitzuverfolgen mit welchen Problemen zu 'kämpfen' ist und welche Ideen ans Tageslicht gefördert werden. Es wird auf alle Fälle wieder den NRW-Summit geben, den Daniel und Stephan wieder organisieren werden. Und und und... Das NEtworking hat sich auf alle Fälle wieder gelohnt. Schade, dass meine Freunde Thomas, Alex und Golo nicht anwesend waren. Well, das nächste GetTogether ist bereits in der Pipe. Kommen wir zurück zu den besagten 24 Stunden des Tages. Wenn man schon einmal in Düsseldorf zugange ist, dann gilt es diese Situation in vollen Zügen auszukosten. Im wahrsten Sinne des Wortes. Und so ging's nach dem Brauhaus weiter durch die Altstadt in eine Cocktail-Bar. Und wieder Networking... yeah! Die Wahl dieser Bar war übrigens optimal ausgefallen, da wir im Laufe der Stunden mehrere Privatvorstellungen deutscher Polizeiarbeit beobachten konnten. Leute, die Serien im Fernsehen nachts live zu sehen, ist um einige Nuancen besser und spannender. Hm, auch Bars haben dann und wann ihre letzte Runde und so ging's dann in den frühen Morgenstunden auf direktem Wege wieder zurück zum Hauptbahnhof. Die Wartezeit auf den nächsten Zug ließ sich mit warmen Frystycksteilchen und Tee problemlos überbrücken. Ab geht's nach Hause... Bis denne, JoKi PS: Mein Tag hatte etwas mehr als 25 Stunden und brachte tierischen Spass. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ein-tag-hat-24-stunden.html'; this.page.identifier = '9271ed1c97_idein-tag-hat-24-stunden'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "eine-privatbrauerei-mal-anders.html": {
    "href": "eine-privatbrauerei-mal-anders.html",
    "title": "Eine Privatbrauerei mal anders - Get Blogged by JoKi",
    "summary": "Eine Privatbrauerei mal anders Copy Markdown View Markdown GitHub Markdown Source Eine Privatbrauerei mal anders Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'eine-privatbrauerei-mal-anders'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'eine-privatbrauerei-mal-anders'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Eine Privatbrauerei mal anders') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-03 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Was ein Tag! Ich liebe es in einem Ort mit eigener Privatbrauerei zu wohnen. \uD83D\uDE0E So auch gestern wieder. Etwa gegen Nachmittag habe ich mich auf den Weg gemacht, schliesslich interessieren die vielen Bands, die gute Stimmung und das Ganze drumherum. Da rentiert sich ruhig mal 'ne Ausnahme gegenüber den üblichen Ausgehzeiten. Und es hat sich rentiert. Bereits die Veranstaltungshinweise auf Rock im Brauhof versprachen einiges, aber vor Ort kam's dann richtig dicke. Zumindest kann ich problemlos behaupten, dass ich mich über zwei bis drei Bands mehr informieren werde. Die Performance war schlichtweg klasse. Außerordentlich gut hat mir übrigens Crime&Passion gefallen. Ein Akustik-Gig der feinsten Art. Ich freue mich die beiden Ende des Jahres im Irish House evtl. wieder zu sehen. Mit einer gelungenen Mischung aus bekannten Lieder - etwa In the Air Tonight oder Without You - und eigenen Stücken stieg die Stimmung im Brauhof spürbar. Vor dem EM-Qualifikationsspiel der deutschen Nationalmannschaft legte Fools Garden noch mal ordentlich einen drauf. Selbstverständlich wurde der internationale Hit Lemon Tree zum Besten gegeben. Das Spiel der deutschen Elf brachte leider irgendwie einen leichten Hänger - nein, nicht ob der Spielweise, die Jungs waren gut - sondern wegen der fehlenden Musik. Okay, man konnte die beiden Halbzeiten prächtig zum Klönen und Spass haben nutzen. Nunja, wir haben uns dann ein wenig verdrückt und noch einen Happen beim Chinesen zu Abend gegessen. Nur Bier am Abend zu trinken soll betrunken machen - habe ich mal gehört. \uD83D\uDE42 Well, leider hat's zeitlich nicht ganz für den Gig von Menschenskinder gereicht, aber die finalen Stücke von Snailshouse konnten wir noch geniessen. Wobei selbst nach Mitternacht der Brauhof immer noch super gefüllt war. Das Wetter hat trotz Bewölkung gehalten und so war es mal wieder ein Spitzeevent in Winnweiler. Mein Dank gilt den Machern und Mitarbeitern der Privatbrauerei Bischoff und RPR1 - klasse, was hier wieder organisiert wurde. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'eine-privatbrauerei-mal-anders.html'; this.page.identifier = '9271ed1c97_ideine-privatbrauerei-mal-anders'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "elfen-lied.html": {
    "href": "elfen-lied.html",
    "title": "Elfen Lied - Get Blogged by JoKi",
    "summary": "Elfen Lied Copy Markdown View Markdown GitHub Markdown Source Elfen Lied Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'elfen-lied'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'elfen-lied'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Elfen Lied') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-18 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ehrlich gesagt, weiß ich nicht wie ich beginnen soll... probieren wir es mal so: Phantastisch, oberhammercool und Yes, Strike! Ich interessiere mich seit einigen Jahren für Animes, aber allein die ersten Minuten von Elfen Lied Episode One haben mich richtig von den Socken gehauen. Gleich die volle Ladung als Auftritt in die Staffel. Dieses Erlebnis hatte ich bisher eigentlich nur bei Kill Bill Vol.1 von Quentin Tarantino und Matrix: Revolutions. Und ähnlich spannend geht's weiter... Man kann hier sehr leicht den Eindruck bekommen, dass es sich um ein Mega-Splatter-Anime handelt, aber die Story um Nyu (aka Lucy), Kouta und Yuka und weiteren Charakteren ist weitaus interessanter und tiefgründiger als man zunächst annehmen dürfte. Inzwischen habe ich die DVDs der Vektoren 1 bis 4 durch und bin auf der Suche nach der Episode 10.5 aka Regenschauer, die leider nicht auf den DVDs vorhanden ist (oder ich noch nicht gefunden habe). Eines kann ich jedoch ohne Probleme sagen. Elfen Lied ist ein Schmuckstück der Anime-Szene und sollte in keiner gut sortierten DVD-Sammlung fehlen. Meines Erachtens seit Akira und Ghost In The Shell wieder ein richtiger Knaller. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'elfen-lied.html'; this.page.identifier = '9271ed1c97_idelfen-lied'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "emoticons-re-activated.html": {
    "href": "emoticons-re-activated.html",
    "title": "Emoticons re-activated - Get Blogged by JoKi",
    "summary": "Emoticons re-activated Copy Markdown View Markdown GitHub Markdown Source Emoticons re-activated Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emoticons-re-activated'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emoticons-re-activated'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Emoticons re-activated') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-02-25 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Some articles, especially the old and transferred ones, had some 'cryptic' content like 'icon _ wink' (without spaces) in the text. Well, that were some placeholders to provide icons on the position. Meanwhile I added those content filters to the current blog system and hopefully you should get nice emoticons instead of stupid text placeholders in my articles. \uD83D\uDE01. Feedback on missing content translation for icons would be very helpful! Thanks for patience and your efforts in advance. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'emoticons-re-activated.html'; this.page.identifier = '9271ed1c97_idemoticons-re-activated'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "emtel-knowledge-series-no1.html": {
    "href": "emtel-knowledge-series-no1.html",
    "title": "Emtel Knowledge Series - Q1/2014 - Get Blogged by JoKi",
    "summary": "Emtel Knowledge Series - Q1/2014 Copy Markdown View Markdown GitHub Markdown Source Emtel Knowledge Series - Q1/2014 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emtel-knowledge-series-no1'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emtel-knowledge-series-no1'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Emtel Knowledge Series - Q1/2014') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-02-16 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Getting more involved into the local ICT community seems to open a lot more possibilities and occasions to participate in various events. Lately, I have been to the first gathering of the freshly created Emtel Knowledge Series. The aim is briefly described as following: \"Emtel is pleased to invite you for an exclusive Knowledge Series Workshop on the theme \"Improving Business Performance Through the Adoption of Innovative Technologies\" organised jointly by the Ministry of ICT and EMTEL This workshop will be a platform that offers intuitive access to innovative technologies, allowing both public and private sector organizations to manage their digital infrastructure more efficiently, to drive growth while saving time and reducing cost.\" -- Source: Eventbrite The Emtel Knowledge Series goes in compliance with Emtel's 25th anniversary celebrations throughout the year and the master of ceremony, Kim Andersen, mentioned that there will be more upcoming events on a quarterly base. As a representative of the Mauritius Software Craftsmanship Community (MSCC) there was absolutely no hesitation to join in and quite frankly it was a well prepared workshop and I'm looking forward to future announcements. Emtel has quite a number a milestones achieved throughout the years, and despite rants over internet connectivity, DIY instructions for In-Car WiFi, or complains about the lack of certain services (FAX, VoIP, multiple SIMs, etc.), it's actually impressive what Emtel did and how they improved the ICT landscape, especially in terms of mobile network, here in Mauritius. But it's also very good to see that the won't rest on their laurels and continue to provide top-notch services for the country and its population. Availability of LTE and IPv6 If I recall correctly, Emtel upgraded their equipment for use of IPv6 already during 2012. And not only plain IPv6 but also the companion services of 6-to4 and 4-to-6 address mapping and tunneling infrastructure. The recent and ongoing movement of the 'Internet of things' it is absolutely recommended and necessary to provide this kind of infrastructure. And it's good to see that at least one communication company is part of this modernisation process. On several occasions I already mentioned that during the startup phase of IOS Indian Ocean Software Ltd. I got in touch with various service providers for telephony and internet connectivity and I chose Emtel since the beginning. Confirmed by the presentations during the day it was a good choice. I guess it's about time to tinker with IPv6 in my own infrastructure and give my internet gateway some attention in those matters. Demonstration of LTE speed In the entrance area there two or three small booth for product and service demonstration. Of course, in promotion of the 4G/LTE network there were only LTE enabled devices like the HTC One, the iPad mini but also the very impressive Samsung NotePro tablet - what a monster! Running the usual Speedtest app from Ookla it was really interesting to see that you could easily reach up to 20Mbps in both directions with a latency of approximately 15-20 milliseconds. Having access to the freely available WiFi networks - one for IPv4 and one for IPv6 - I did some test runs with various servers in different areas of the globe during the presentations. Of course, due to international cabling the results vary... The test results with servers in Dubai, Cape Town or Singapore ranged between 3.0 and 6.4 Mbps while offering pings between 300ms and 1.000ms. The advantage of increased speed is inevitable but the latency values are still comparable to my findings on WiMAX or on 3G network. Most interestingly, the package prices of LTE connectivity are lower than for WiMAX and I'm already taking into consideration to swap networks given reliable network coverage in my area. My main concern about that switch is only related to the bandwidth... Networking in the name of MSCC As briefly mentioned above I was about to combine two approaches for this workshop. Of course, getting latest information and updates on Emtel services available, especially for my business here on the west coast of the island, but also to meet and greet new people for the MSCC. And I think it was very positive on both sides. Let me quickly describe some of the key aspects that happened during the day: Meeting with Arnaud Meslier, Microsoft to exchange latest information on the upcoming Global Windows Azure Bootcamp (GWAB). Unfortunately, we are little bit behind schedule with our plannings but still green. Got in touch with Bruce Hough, Emtel to learn about the potential availability of 4G/LTE in Flic en Flac. Currently, there is coverage at La Preneuse, Black River and I should request another site survey to check out the feasibility. Got introduced to Adrian Christian, Emtel which showed interest in my long-lasting service requests and reasoning for a number of mobile services that Emtel is currently not offering but should (IMHO). Had a quick chat with Mrs. Madhub, Data Protection Commissioner on potential corporation with the MSCC and LUGM for a planned conference during September. Additionally, a number of various other interesting chats... At the moment, I'm catching up on a couple of business cards in order to provide more background information about the MSCC, and to create a better awareness of MSCC within the local IT businesses. Raffle - Surprise, surprise! Surprisingly, during the morning sessions it was announced that there will be a raffle by the end of the day, and attendees are invited to join in with their lanyards. Originally, I had the intention to leave before lunch break but hey why not just stay a bit longer and see what's going to happen during the raffle... First (and only) prize to win was an Apple iPad mini WiFi Cellular, 16GB Black. In the nature of the event this device is LTE-enabled and ready to rumble the online streams. How do I know so many details? After a very long adventure with numerous draws using a software appication calledEasy Raffle and noone in the room to claim the prize (although I was very very close one time), it was finally decided that instead of targetted 3 digits, anyone whose last 2 digits match would win. Well, first trial on the new concept beared no winner, second trial again nothing, and then finally the lucky number was declared: 25 - as in 25th anniversary of Emtel. My badge number was 47225... surprise, surprise! Another tablet added to the collection of devices... Thanks Emtel! Resume of the day The event was split in multiple concepts and the transitions between presentations on various topics, the panel discussions, the product and service demonstration outside the auditorium, and the placement of the breaks for food and networking were smooth. Overall, a well-organised workshop and it would be another interesting experience to join the next workshop in Q2. Furthermore, I would also welcome that Driving Business Forward by Knowledge 7 as of last year is about to continue. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'emtel-knowledge-series-no1.html'; this.page.identifier = '9271ed1c97_idemtel-knowledge-series-no1'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "emtel-knowledge-series-no3.html": {
    "href": "emtel-knowledge-series-no3.html",
    "title": "Emtel Knowledge Series - Q2/2014 - Get Blogged by JoKi",
    "summary": "Emtel Knowledge Series - Q2/2014 Copy Markdown View Markdown GitHub Markdown Source Emtel Knowledge Series - Q2/2014 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emtel-knowledge-series-no3'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emtel-knowledge-series-no3'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Emtel Knowledge Series - Q2/2014') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-06-20 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); From Cyber Island to Smart Mauritius Cyber Island? Smart Mauritius? - What is Emtel talking about? \"With the majority of the population living in urban environments today, the concept of \"Smart Cities\" has become an urgent necessity. \"Smart Cities\" refer to an urban transformation which, by using latest ICT technologies makes cities more efficient. Many Governments are setting out ambitious plans to build the cities of the future based on massive connectivity, high bandwidth communications, intelligent sensors and analysis of huge volumes of data. Various researches have shown four key enablers for smart city success - Government leadership, suitable technology infrastructure, solid public-private partnerships and engaged citizens. It is around these enabling factors that telecoms companies can play a vital role in assisting governments to deliver on the smart city vision.\" The Emtel Knowledge Series goes in compliance with Emtel's 25th anniversary celebrations throughout the year and the master of ceremony, Kim Andersen, mentioned that there will be more upcoming events on a quarterly base. As a representative of the Mauritius Software Craftsmanship Community (MSCC) there was absolutely no hesitation to join in again. Following my visit to thefirst Emtel Knowledge Series workshop back in February this year, it was great to have another opportunity to meet and exchange with technology experts. But quite frankly what is it with those buzz words... As far as I remember and how it was mentioned \"Cyber Island\" is an old initiative from around 2005/2006 which has been refreshed in 2010. It implies the empowerment of Information & Communication Technologies (ICT) as an essential factor of growth by the government here in Mauritius. Actually, the first promotional period of Cyber Island brought me here but that's another story. The venue and its own problems Like last time the event was organised and held at the Conference Hall at Cyber Tower I in Ebene. As I've been working there for some years, I know about the frustrating situation of finding a proper parking. So, does Smart Island include better solutions for the search of parking spaces? Maybe, let's see whether I will be able to answer that question at the end of the article. Anyway, after circling around the tower almost two times, I finally got a decent space to put the car, without risking to get a ticket or damage actually. International speakers and their experience Once again, Emtel did a great job to get international expertise onto the stage to share their experience and vision on this kind of embarkment. Personally, I really appreciated the fact they were speakers of global reach and could provide own-experience knowledge. Johan Gott spoke about the fundamental change that the Swedish government ignited in order to move their society and workers' environment away from heavy industry towards a knowledge-based approach. Additionally, we spoke about the effort and transformation of New York City into a greener and more efficient Smart City. Given modern technology he also advised that any kind of available Big Data should be opened to the general public - this openness would provide a playground for anyone to garner new ideas and most probably solid solutions of which no one else thought about before. Emtel Knowledge Series on moving from Cyber Island to Smart Mauritus Later during the afternoon that exact statement regarding openness to and transparency of government-owned Big Data has been emphasised again by the Danish speaker Kim Andersen and his former colleague Mika Jantunen from Finland. Mika continued to underline the important role of the government to provide a solid foundation for a knowledge-based society and mentioned that Finnish citizens have a constitutional right to broadband connectivity. Next to free higher (tertiary) education Finland already produced a good number of innovations, among them are: First country to grant voting rights to women Free higher education Constitutional right to broadband connectivity Nokia Linux Angry Birds Sauna and others... General access to internet via broadband and/or mobile connectivity is surely a key factor towards Smart Cities, or better said Smart Mauritius given the area dimensions and size of population. CTO Paul Valette gave the audience a brief overview of the essential role that Emtel will have to move Mauritius forward towards a knowledge-based and innovation-driven environment for its citizen. What I have seen looks really promising and with recently published information that Mauritians have 127% of mobile capacity - meaning more than 1 mobile, smartphone or tablet per person - it will be crucial to have the right infrastructure for these connected devices. How would it be possible to achieve a knowledge-based society? YouTube to the rescue! Seriously, gaining more knowledge will require to have fast access to educational course material as explained by Dr Kaviraj Sukon, General Director of the Open University of Mauritius. According to him a good number of high-profile universities in the world have opened their course libraries to the general public, among them EDX, Coursera and Open University. Nowadays, you're actually able and enabled to learn for and earn a BSc or even MSc certification on your own pace - no need to attend classed on campus. It was really impressive to see the number of available hours - more than enough for a life-long learning experience! Networking in the name of MSCC As briefly mentioned above I was about to combine two approaches for this workshop. Of course, getting latest information and updates on Emtel services available, especially for my business here on the west coast of the island, but also to meet and greet new people for the MSCC. And I think it was very positive on both sides. Let me quickly describe some of the key aspects that happened during the day: Met with Arnaud Meslier and Kellie, both Microsoft to swap latest information on IT events. Hereby, I got an invite to Microsoft Windows Phone 8.1 Dev Camp. Got in touch with Arvin Lockee, Emtel to check our options to meet with the data team, and seizing the opportunity to have a visiting tour at the Emtel Data Centre. Had a great chat with Avinash Meetoo, Knowledge 7, Kim Andersen and Mika Jantunen about the situation of teaching and learning in general and specifically in the private sector here in Mauritius. Additionally, a number of various other interesting chats... Once again, I'm catching up on a couple of business cards in order to provide more background information about the MSCC, and to create a better awareness of MSCC within the local IT businesses. There is more to come soon! Resume of the day The number of attendees during this event has been doubled or even tripled this time. The whole organisation has been improved massively and the combination of presentation and summarizing panel discussions was better than during the previous workshop back in February. Overall, once again a well-organised workshop and I'm already looking forward to join the next workshop in Q3. Update End of July we finally managed to visit the Emtel Data Centre in Arsenal. It was an interesting opportunity for some of our MSCC members. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'emtel-knowledge-series-no3.html'; this.page.identifier = '9271ed1c97_idemtel-knowledge-series-no3'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "emtel-wingle-aka-huawei-e8231.html": {
    "href": "emtel-wingle-aka-huawei-e8231.html",
    "title": "Review: Emtel Wingle a.k.a. Huawei E8231 - Get Blogged by JoKi",
    "summary": "Review: Emtel Wingle a.k.a. Huawei E8231 Copy Markdown View Markdown GitHub Markdown Source Review: Emtel Wingle a.k.a. Huawei E8231 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emtel-wingle-aka-huawei-e8231'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'emtel-wingle-aka-huawei-e8231'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Review: Emtel Wingle a.k.a. Huawei E8231') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-11-05 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); How would you call the approach of taking the concept of \"In-Car WiFi\" and the marketing of \"Home & Office Broadband\" to the next level? Well, it's called Emtel Wingle. The Wingle is an artificial word combination of WiFi dongle, and Emtel seems to be right on spot with this device. Identical to a regular USB surf stick, the Wingle offers an instant WiFi hot-spot as soon as powered up. Of course, you can plug into your laptop or desktop PC to surf the internet but with the hotspot you can also provide a shared connection for up to 10 devices via wireless network. The device also works on plain power supply coming from a wall socket or maybe from a portable USB charger in the vehicle. Occasional usage Personally, I use the Wingle every time I'm on tour. On several outings I simply plugged it into the mobile USB charger in my car, and the rest of the family was able to be online - either checking Facebook, having instant chats or having a Skype session with relatives abroad - all while driving around the island. It's kind of fun actually. Furthermore, it's also a nice option while hanging out on the beach. The distance range of the Wingle's wireless network is quite good. Furthermore, during the regular meetings of the Mauritius Software Craftsmanship Community (MSCC) I give access to my hotspot to other community members, and they love the experience, too. Being able to get access to online repositories on github or Visual Studio online, or logging into virtual machines on the cloud is a very positive aspect for our IT get-togethers. Nowadays, it's an always-on situation for most people working in IT. Even though you should keep an eye on proper balance it's a fact that one is regularly online. Connection speeds Well, the Emtel Wingle is a regular USB surf stick and offers internet bandwidth up to 21 MBit/s on the download, and up to 7 MBit/s for uploads. During the last couple of weeks (okay, already months) I did a variety of speed test at different locations. The performance is okay and limitations are given by the international bandwidth constraints here on the island. Web interface for administration As soon as you plug the device into your computer it fires up its default website for administration. The initial page is \"public\" and provides information about your current connection status and bandwidth usage. In order to get more details and additional features you have to log in. Emtel Wingle: Statistics information about your bandwidth usage Next to more statistics about bandwidth usage and connected devices you also have the ability to send and receive SMS with your device. Side-note: Emtel sends you an informal message in case your bandwidth usage reaches a certain threshold on your subscription (applies only to post-paid SIM cards). The Settings area is equivalent to the options you would expect from a full-featured internet router/gateway. You have the ability to tweak the configuration for your wireless network (WLAN) and other sophisticated services, incl. WLAN MAC filter, DHCP settings, enabling virtual servers and DMZ settings for advanced configurations. Emtel Wingle: Lots of settings for your personalised configuration and optimal usage Actually, it would be great if Emtel would provide a demo website to explore the Wingle's administration online for everyone. Resume Sincerely, I love this type of USB surf stick. Not only I'm able to get my laptop only but also provide internet connection sharing via wireless network to 10 additional devices. This is absolutely great during meetups with others. The handling is just plug-n-play and you're ready to use it. At the time of writing this article, Emtel offered the Wingle for Rs. 1,699 inclusive VAT. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'emtel-wingle-aka-huawei-e8231.html'; this.page.identifier = '9271ed1c97_idemtel-wingle-aka-huawei-e8231'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "enabling-dns-for-ipv6-infrastructure.html": {
    "href": "enabling-dns-for-ipv6-infrastructure.html",
    "title": "Enabling DNS for IPv6 infrastructure - Get Blogged by JoKi",
    "summary": "Enabling DNS for IPv6 infrastructure Copy Markdown View Markdown GitHub Markdown Source Enabling DNS for IPv6 infrastructure Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'enabling-dns-for-ipv6-infrastructure'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'enabling-dns-for-ipv6-infrastructure'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Enabling DNS for IPv6 infrastructure') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-03-12 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After successful automatic distribution of IPv6 address information via DHCPv6 in your local network it might be time to start offering some more services. Usually, we would use host names in order to communicate with other machines instead of their bare IPv6 addresses. During the following paragraphs we are going to enable our own DNS name server with IPv6 address resolving. Configure IPv6 on your Linux system DHCPv6: Provide IPv6 information in your local network Enabling DNS for IPv6 infrastructure Accessing your web server via IPv6 Piece of advice: This is based on my findings on the internet while reading other people's helpful articles and going through a couple of man-pages on my local system. What's your name and your IPv6 address? $ sudo service bind9 status * bind9 is running If the service is not recognised, you have to install it first on your system. This is done very easy and quickly like so: $ sudo apt-get install bind9 Once again, there is no specialised package for IPv6. Just the regular application is good to go. But of course, it is necessary to enable IPv6 binding in the options. Let's fire up a text editor and modify the configuration file. $ sudo nano /etc/bind/named.conf.options acl iosnet { 127.0.0.1; 192.168.1.0/24; ::1/128; 2001:db8:bad:a55::/64; }; listen-on { iosnet; }; listen-on-v6 { any; }; allow-query { iosnet; }; allow-transfer { iosnet; }; Most important directive is the listen-on-v6. This will enable your named to bind to your IPv6 addresses specified on your system. Easiest is to specify any as value, and named will bind to all available IPv6 addresses during start. More details and explanations are found in the man-pages of named.conf. Save the file and restart the named service. As usual, check your log files and correct your configuration in case of any logged error messages. Using the netstat command you can validate whether the service is running and to which IP and IPv6 addresses it is bound to, like so: $ sudo service bind9 restart $ sudo netstat -lnptu | grep \"named\\W*$\" tcp 0 0 192.168.1.2:53 0.0.0.0:* LISTEN 1734/named tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN 1734/named tcp6 0 0 :::53 :::* LISTEN 1734/named udp 0 0 192.168.1.2:53 0.0.0.0:* 1734/named udp 0 0 127.0.0.1:53 0.0.0.0:* 1734/named udp6 0 0 :::53 :::* 1734/named Sweet! Okay, now it's about time to resolve host names and their assigned IPv6 addresses using our own DNS name server. $ host -t aaaa www.6bone.net 2001:db8:bad:a55::2 Using domain server: Name: 2001:db8:bad:a55::2 Address: 2001:db8:bad:a55::2#53 Aliases: www.6bone.net is an alias for 6bone.net. 6bone.net has IPv6 address 2001:5c0:1000:10::2 Alright, our newly configured BIND named is fully operational. Eventually, you might be more familiar with the dig command. Here is the same kind of IPv6 host name resolve but it will provide more details about that particular host as well as the domain in general. $ dig @2001:db8:bad:a55::2 www.6bone.net. AAAA More details on the Berkeley Internet Name Domain (bind) daemon and IPv6 are available in Chapter 22.1 of Peter Bieringer's HOWTO on IPv6. Setting up your own DNS zone Now, that we have an operational named in place, it's about time to implement and configure our own host names and IPv6 address resolving. The general approach is to create your own zone database below the bind folder and to add AAAA records for your hosts. In order to achieve this, we have to define the zone first in the configuration file named.conf.local. $ sudo nano /etc/bind/named.conf.local // // Do any local configuration here // zone \"ios.mu\" { type master; file \"/etc/bind/zones/db.ios.mu\"; }; Here we specify the location of our zone database file. Next, we are going to create it and add our host names, our IP and our IPv6 addresses. $ sudo nano /etc/bind/zones/db.ios.mu $ORIGIN . $TTL 259200 ; 3 days ios.mu IN SOA ios.mu. hostmaster.ios.mu. ( 2014031101 ; serial 28800 ; refresh (8 hours) 7200 ; retry (2 hours) 604800 ; expire (1 week) 86400 ; minimum (1 day) ) NS server.ios.mu. $ORIGIN ios.mu. server A 192.168.1.2 server AAAA 2001:db8:bad:a55::2 client1 A 192.168.1.3 client1 AAAA 2001:db8:bad:a55::3 client2 A 192.168.1.4 client2 AAAA 2001:db8:bad:a55::4 With a couple of machines in place, it's time to reload that new configuration. Note: Each time you are going to change your zone databases you have to modify the serial information, too. Named loads the plain text zone definitions and converts them into an internal, indexed binary format to improve lookup performance. If you forget to change your serial then named will not use the new records from the text file but the indexed ones. Or you have to flush the index and force a reload of the zone. This can be done easily by either restarting the named: $ sudo service bind9 restart or by reloading the configuration file using the name server control utility - rndc: $ sudo rndc reconfig Check your log files for any error messages and whether the new zone database has been accepted. Next, we are going to resolve a host name trying to get its IPv6 address like so: $ host -t aaaa server.ios.mu. 2001:db8:bad:a55::2 Using domain server: Name: 2001:db8:bad:a55::2 Address: 2001:db8:bad:a55::2#53 Aliases: server.ios.mu has IPv6 address 2001:db8:bad:a55::2 Looks good. Alternatively, you could have just ping'd the system as well using the ping6 command instead of the regular ping: $ ping6 server PING server(2001:db8:bad:a55::2) 56 data bytes 64 bytes from 2001:db8:bad:a55::2: icmp_seq=1 ttl=64 time=0.615 ms 64 bytes from 2001:db8:bad:a55::2: icmp_seq=2 ttl=64 time=0.407 ms ^C --- ios1 ping statistics --- 2 packets transmitted, 2 received, 0% packet loss, time 1001ms rtt min/avg/max/mdev = 0.407/0.511/0.615/0.104 ms That also looks promising to me. How about your configuration? Next, it might be interesting to extend the range of available services on the network. One essential service would be to have web sites at hand. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'enabling-dns-for-ipv6-infrastructure.html'; this.page.identifier = '9271ed1c97_idenabling-dns-for-ipv6-infrastructure'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "entwicklerkonferenz-2006-tag-1.html": {
    "href": "entwicklerkonferenz-2006-tag-1.html",
    "title": "13. Entwicklerkonferenz Visual FoxPro - Tag 1 - Get Blogged by JoKi",
    "summary": "13. Entwicklerkonferenz Visual FoxPro - Tag 1 Copy Markdown View Markdown GitHub Markdown Source 13. Entwicklerkonferenz Visual FoxPro - Tag 1 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'entwicklerkonferenz-2006-tag-1'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'entwicklerkonferenz-2006-tag-1'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '13. Entwicklerkonferenz Visual FoxPro - Tag 1') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-09 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nachfolgend ein paar Eindrücke und Erlebnisse während des ersten Tages der Entwicklerkonferenz zu Visual FoxPro, SQL Server und .NET Framework. Der frühe Vogel fängt den Wurm Ja, in der Tat, so kam es mir gerade heute morgen ein wenig vor. Nicht nur die Tatsache, dass ich die Session zu Active FoxPro Pages 3.0 als eine der ersten zum Besten geben darf; nein, auch noch gleich im größten Raum - dem Auditorium - der Konferenz. Well, well, was sein, muss sein... Es besteht hierbei leider die Gefahr, dass sich einige der Anwesenden in den letzten Reihen platzieren und die Distanz im Vergleich zu den anderen Räumen doch ein klein wenig weiter ist. Hallo Echo... Die Session lief für meinen Geschmack gut. Um ehrlich zu sein, war ich angetan, dass ich absolut im zeitlichen Rahmen von 75 Minuten geblieben bin. Im Gegenteil, am Ende blieben sogar noch einige Minuten für Fragen und Antworten zur freien Verfügung. Scheinbar wirkt sich das 'Session-Training' auf den monatlichen Stammtischen positiv hier auf der Konferenz aus. Interessanterweise fiel mir während der Session noch ein ziemlich interessantes und wirkungsvolles Einsatzgebiet für die AFP ein: Reportausgabe im Web aka Reporting Services. Sämtliche Werkzeuge liegen zu Füssen - VFP Datenbank, VFP Reports und AFP auf einem Webserver. Ein wenig HTML samt JavaScript für die optische Darstellung und Benutzerinteraktion und schon hat man eine brauchbare Lösung. Hm, im Laufe der Konferenz habe ich ein kleines Frontend für AFP Reporting Services erstellt. D-VFPX - VFP 9.0 und VFPX/VFPY Visual FoxPro und die Community - zwei unzertrennliche Komponenten. Eine Komposition sozusagen. Nur irgendwie hat das zeitlich nicht bei mir hingehauen. Achja, ich war in sehr guten Gesprächen mit anderen Entwicklern vertieft. Ich fand die Gelegenheit wirkungsvoller als die Demonstration der verschiedenen Projekte von VFPX u begutachten. Auch unter dem Gesichtspunkt, dass ich bereits zwei Komponenten - GDIPlusX und OutlookBar2003 - im Projekt: Sunshine erweitert habe. Weitere Lösungen aus VFPX werde ich mir sicherlich mal bei Bedarf zu Gemüte führen. D-PDF - PDF in Form Ein wenig verspätet durch das vorzügliche Mittagessen versuchte ich den detaillierten Informationen von Kirsten Hinrichs in Bezug auf die Formularfähigkeiten des PDF-Formats zu folgen. Die Inhaltstiefe des Sachgebiets, die Art und Weise der Wissensvermittlung und die umfangreichen Erläuterungen zu auftretenden Problemen für diese 'undokumentierte' Funktionalität in den preisgünstigen PDF-Lösungen von Adobe waren gut. Ich habe es dennoch nicht so ganz verstanden. Was jedoch weniger an Kirstens Session sondern vielmehr an mir lag. Erschwerend der Kampf mit der Schläfrigkeit nach der Mahlzeit. Ich werde mir hier auf alle Fälle die Sessionnotizen durchlesen. E-REP1 - Sedna Reporting Enhancements Haha... die Report Engine in VFP 9.0 ist schon wesentlich verbessert worden. Neben der Einbindung von GDI+ zur grafischen Darstellung (insbesondere beim Zoom) und den verfeinerten Einstellungen zur optischen Konfiguration, wird das Service Pack 2 die Messlatte wieder ein gutes Stück nach oben verlegen. Die Erweiterbarkeit der VFP 9.0 Report Engine wurde erneut verbessert und vereinfacht. Wo in den vorherigen Versionen verstreut einzelne Teile eines Reports ein wenig unkoordiniert in der Lage verstreut rumlagen, bieten die Veränderungen im kommenden Service Pack 2 ein \"Zwiebelprinzip\" eines Reports, ohne Limitierung \uD83D\uDE09 Die Ideen von Lisa waren übrigens auch einer der Gründe für die Implementierung der bereits genannten AFP Reporting Services. Diese sind sozusagen während der Session nebenbei entstanden. Hm, ich denke, dass ich hierzu einen eigenen Artikel schreiben werde. D-CASE - Use Case Analyse in Projekten Use Cases oder auf deutsch Anwendungsfällen ermöglichen die Übertragung eines alltäglichen Problems in die Semantik der Software-Entwicklung. Anwendungsfälle sind Bestandteil der Unified Modeling Language (UML) und werden im Rahmen der Systemanalyse zusammen mit dem Kunden aufgenommen und dokumentiert. Auf diese Weise kann man durch Fragestellungen und der Erkennung von abstrakten Zusammenhängen auf definierte Art das System in Teilen und im Ganzen beschreiben. Nicht nur, dass man selbst mit dem Kunden das Szenario beschreibt und somit die Situationen konkretisieren kann, sondern auch später in den Entwicklungsphasen helfen Use Cases dem Entwickler bei der Implementierung der Software - ohne Überraschungen erleben zu müssen. Patrick gab zunächst einen Überblick über diverse Modellelemente der UML wider und die Session konzentrierte sich danach auf die Modellierung eines Use Case Diagramms mit den verschiedenen Relationen zwischen Akteuren und Anwendungsfällen. Zunächst mag diese Herangehensweise für einen Entwickler als zeitaufwändig und überflüssig erscheinen, aber die produzierten Diagramme sind zu späteren Zeitpunkten den Aufwand immer wert, da weitaus weniger Fragen diskutiert werden müssen. ResumÃ© zum ersten Konferenztag Nach einem arbeitsreichen Start am Morgen lief der Tag sozusagen sehr gediegen über die Bühne. Zwar habe ich sehr viel Zeit mit Networking und Gesprächen mit anderen Teilnehmern verbracht; was ich übrigens wirklich sehr begrüße und schätze, aber dennoch blieb Zeit sich in überaus interessante Vorträge mit Anregungen und Ideen versorgen zu lassen. Den Abend haben wir dann mit der Vertiefung etlicher Sessionthemen im Hotel-Bistro vertieft. Insbesondere das Wiedersehen mit einigen Besuchern der Devcon 2006 in Prag - etwa Juri Tschutenko - freute mich besonders. Community at its best. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'entwicklerkonferenz-2006-tag-1.html'; this.page.identifier = '9271ed1c97_identwicklerkonferenz-2006-tag-1'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "entwicklerkonferenz-2006-visual-foxpro-tag-2.html": {
    "href": "entwicklerkonferenz-2006-visual-foxpro-tag-2.html",
    "title": "13. Entwicklerkonferenz Visual FoxPro - Tag 2 - Get Blogged by JoKi",
    "summary": "13. Entwicklerkonferenz Visual FoxPro - Tag 2 Copy Markdown View Markdown GitHub Markdown Source 13. Entwicklerkonferenz Visual FoxPro - Tag 2 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'entwicklerkonferenz-2006-visual-foxpro-tag-2'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'entwicklerkonferenz-2006-visual-foxpro-tag-2'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '13. Entwicklerkonferenz Visual FoxPro - Tag 2') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-10 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Auszeit... Nach einer kurzen Nachtphase geht's ein wenig 'verkatert' in die nächste Runde der diesjährigen Devcon. Zum Auftakt des Tages schaue ich mir zunächst die Verbesserungen und Erweiterungen der VFP 9.0 Reportengine im Zuge des kommenden Service Pack 2 an. D-WEB1 D-NET4 D-GUI D-CLR Ausklang des Tages Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'entwicklerkonferenz-2006-visual-foxpro-tag-2.html'; this.page.identifier = '9271ed1c97_identwicklerkonferenz-2006-visual-foxpro-tag-2'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "es-geschehen-noch-zeichen-und-wunder.html": {
    "href": "es-geschehen-noch-zeichen-und-wunder.html",
    "title": "Es geschehen noch Zeichen und Wunder! - Get Blogged by JoKi",
    "summary": "Es geschehen noch Zeichen und Wunder! Copy Markdown View Markdown GitHub Markdown Source Es geschehen noch Zeichen und Wunder! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-geschehen-noch-zeichen-und-wunder'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-geschehen-noch-zeichen-und-wunder'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Es geschehen noch Zeichen und Wunder!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-17 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Im ersten Moment traute ich meinen Augen nicht: CD-Key-Überprüfung als Plug-in für Firefox Microsoft hat ein Plug-in für Mozilla-Browser wie Firefox veröffentlicht, mit dem das Windows-Download-Center den... Wow, Microsoft sollte es wirklich geschafft haben einen weiteren Schritt Richtung OpenSource und Browseralternativen gegangen zu sein. Ich bin aktuell etwas perplex... Ein großer Schritt in die richtige Richtung. Weiter so! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'es-geschehen-noch-zeichen-und-wunder.html'; this.page.identifier = '9271ed1c97_ides-geschehen-noch-zeichen-und-wunder'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "es-lebt-wieder.html": {
    "href": "es-lebt-wieder.html",
    "title": "Es lebt wieder... - Get Blogged by JoKi",
    "summary": "Es lebt wieder... Copy Markdown View Markdown GitHub Markdown Source Es lebt wieder... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-lebt-wieder'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-lebt-wieder'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Es lebt wieder...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-01-17 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nach langer Zeit gibt es doch noch Lebenszeichen... Sorry, verehrte Leser, aber aus beruflichen Gründen war das Blog leider über längere Zeit nicht verfügbar. Inzwischen gab es einen weiteren Server- und Softwarewechsel, so dass ich in den kommenden Tagen ebenfalls die alten Beiträge noch restaurieren werde. Ich hoffe, dass ich bis Ende diesen Monats wieder ein vollständiges Blog für euch bieten kann. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'es-lebt-wieder.html'; this.page.identifier = '9271ed1c97_ides-lebt-wieder'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "es-muss-nicht-immer-foxpro-sein.html": {
    "href": "es-muss-nicht-immer-foxpro-sein.html",
    "title": "Es muss nicht immer FoxPro sein... - Get Blogged by JoKi",
    "summary": "Es muss nicht immer FoxPro sein... Copy Markdown View Markdown GitHub Markdown Source Es muss nicht immer FoxPro sein... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-muss-nicht-immer-foxpro-sein'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-muss-nicht-immer-foxpro-sein'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Es muss nicht immer FoxPro sein...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-17 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Der Betreff mag vielleicht irritiere, aber der gestrige VFP-Stammtisch war ein besonderer Abend... Zu meiner \"Schande\" muss ich gestehen, dass das gestrige Thema - PostgreSQL 8.0 - mangels installierter Software ausgefallen ist. Daher die gute Nachricht an die Abwesenden: Ihr habt nix verpasst... Naja, eiegntlich doch. Mangels Thema haben wir uns zuerst Ersatzthemen überlegt, aber letztendlich sind wir bei der Nachlese zur 12. Entwicklerkonferenz hängen geblieben und haben den gesamten Abend in der gewohnten, gemütlichen Atmosphäre miteinander palavert. Wow, ein Traumabend. Anfangs habe ich ein wenig vom Microsoft Launch Event vom letzten Montag in München erzählt und meine Eindrücke vom CLIP GetTogether widergegeben. Dabei habe ich auch wenig von Office \"12\" und Exchange Service Pack 2 berichtet. Ja, insgesamt ein lockerer Einstieg. Weiter ging's dann mit der Konferenz in Frankfurt und den Erlebnissen... wortwörtlich. Glücklicherweise waren noch andere Konferenzteilnehmer gestern Abend auf dem Stammtisch und so konnten wir den anderen ein duten Überblick mit unterschiedlichen Meinungen und Interpretationen geben. Interessant war unter anderem der Aspekt, dass die meisten überhaupt nicht wussten, dass die Zimmerpreise im Lindner Hotel speziell für die Konferenz gesenkt wurden. So hatte sich das Schwabenteam wie in den Vorjahren bereits in einer Pension einquartiert, während die Übernachtungskosten im Lindner nahezu identisch waren... Die Kritik ging hierbei in Richtung dFPUG. Leider sind etliche Informationen für eine ausreichende Zeitplanung vor und während der Konferenz viel zu kurzfristig, so die einheitliche Meinung der Anwesenden... Da wir bereits voll im Thema VFP-Devcon waren, liessen sich natürlich auch nicht die Erfahrungen von Gerd und mir bezüglich der Prager Konferenz unter den Tisch schweigen. Auch hier kann ich nur meine Empfehlung aussprechen: Nutzt die Chance und schaut euch mal die Konferenz in Prag an. Es rentiert sich und es stehen ausreichend englischsprachige Sessions für alle Tage zur Verfügung. Wir hatten effektiv keine Leerzeit während der drei Tage. Achja, bevor ich es vergesse... Der Termin im Dezember liegt ein wenig ungünstig - 21. Dezember - und wir haben angefangen darüber zu diskutieren, ob und wie und vielleicht nicht an einem anderen Termin und überhaupt das Treffen im Dezember stattfindet. Wir haben uns geeignigt, dass wir wieder einmal eine Mailabstimmung starten... Yeah! Vote, baby! Abstimmung über Termin Stammtisch Dezember (Mehrfachnennung erwünscht): [ ] Mittwoch, 7. Dezember [ ] Mittwoch, 14. Dezember [ ] Mittwoch, 21. Dezember jeweils ab ca. 19:30 (wie immer) Bitte gebt mir alle möglichst zeitnah Rückmeldung, so dass ich noch mit unserer freundlichen Wirtin entsprechend was vereinbaren kann. Danke! Für das nächste Treffen ist zusätzlich 'ne kleinere Weihnachtsverlosung angedacht. Ich hab' aktuell ein paar Goodies von Microsoft, der INETA und der dFPUG. Falls noch jemand etwas sponsorn möchte, bitte Bescheid geben... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'es-muss-nicht-immer-foxpro-sein.html'; this.page.identifier = '9271ed1c97_ides-muss-nicht-immer-foxpro-sein'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "es-wird-konkreter---.html": {
    "href": "es-wird-konkreter---.html",
    "title": "Es wird konkreter... - Get Blogged by JoKi",
    "summary": "Es wird konkreter... Copy Markdown View Markdown GitHub Markdown Source Es wird konkreter... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-wird-konkreter---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'es-wird-konkreter---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Es wird konkreter...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-04 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ja, die englische Anmeldung für die weltweit größte Konferenz zu Visual FoxPro lädt auch dieses Jahr wieder ein. Es handelt sich dabei um die VFP Devcon in Prag - jawohl. Seit einigen Tagen könnt ihr euch auf der Website von Microsoft registrieren und anmelden: https://www.microsoft.cz/akce/en/vfpdevcon/ Leute für den Kostenbeitrag gibt's eigentlich keinerlei Ausflüchte der Konferenz fern zu bleiben. Also, wir sehen uns dann im Juni auf der Devcon in Prag... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'es-wird-konkreter---.html'; this.page.identifier = '9271ed1c97_ides-wird-konkreter---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "essentials-for-software-developer.html": {
    "href": "essentials-for-software-developer.html",
    "title": "Essentials for software developer or any computer job - Get Blogged by JoKi",
    "summary": "Essentials for software developer or any computer job Copy Markdown View Markdown GitHub Markdown Source Essentials for software developer or any computer job Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'essentials-for-software-developer'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'essentials-for-software-developer'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Essentials for software developer or any computer job') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-05-13 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); I have been talking about the following on many occasions during the last years already. Actually, it is always part of my consulting services when companies ask for advice on what they should do to improve the overall satisfaction of their software developers. But in general, it's pretty simple: The following are my essentials for any software developer or craftsman that is taking her or his profession seriously. Originally, I started my list with the first four topics only but experience taught me that there is more to this. And even nowadays, I would clearly say that this overview scratches the surface only. Touch-typing After being introduced to a team of software developers and/or testers by project or company management, I really enjoy myself to ask this particular question at a very early stage: \"Do you touch-type?\" I can tell you the expressions of their faces says more than any written book I've read so far. It's absolutely amazing how such a simple question puts everyone in the room into a dazzling state and gets them to look, uhm sometimes even to gaze at me with a touch of glare in their eyes, as if I just spoke in an unknown language. The reactions are mixed but usually the responses are similar to this short selection: \"What?\" \"Sorry, I don't understand.\" \"What do mean by touch-typing?\" \"No... ?\" Honestly, I have to admit that I totally understand this kind of answer based on the fact that absolutely nowhere young programmers or developers are educated and trained in an efficient way to use their daily tools of future work. Seriously, that question would be comparable to ask an architect whether she uses a calculator on her job. Only, that her reply would be more like this: \"Of course.\" So what is the actual problem about the fact that most developers I meet here on the island are not capable to touch-type, or sometimes don't even know what I'm actually talking about? Sincerely, I have no straight answer to this but it seems obvious to all of them that working 6+ hours per day at the PC, using the keyboard to get the work done, doesn't necessarily require to be fast and efficient. Definition of TOUCH-TYPE : to type by the touch system -- touch typist noun Source: Definition taken from Merriam-Webster - https://www.merriam-webster.com/dictionary/touch-type Know your tools... well, getting a glimpse at the various curriculum at the tertiary educational institutes reveals a little bit more. Touch-typing isn't part of the education and therefore ignored. Any software developer without touch-type capability would have difficulties with my equipment - das keyboard Ultimate: das keyboard Ultimate is a keyboard with blank keys and the ultimate experience of touch-typing Most people really don't understand the consequences. It's not only about time, effort and cost but also plays a great role in daily motivation. I know that developers want to write code first, uhm, maybe I should say to type code first. But what's the purpose of creating wonderful code when your physical capabilities are far behind your mental ones? It's like producing a lot of output that gets finally stuck in the I/O buffers of your body. Latest generation of hardware and software Obviously this should be a no-brainer but reality bites and quite often developers' machines are not top-notch. There are various reasons for that and hundreds of excuses made by an employer but frankly speaking at least every two to three years latest it is necessary to upgrade the work equipment. Again, this is also something I clearly advice to other companies: Faster machine, less time-consuming and more efficiency of their developers and software testers. When I begin to work with new teams I like to get a better understanding of their opinion about their work, and as you might have guessed already... The computer they are using is usually part of those conversations. Either because I ask about it or they already tell me that they are not happy about their PC. Usually, they have more powerful machines at home, either for gaming or to develop on private projects. Imagine the frustration they have coming to work and only finding a dead-slow machine. The pain of waiting until the IDE has started and loaded the recent projects finally. Some of them even switch on the machine, wait to login and then go for their cup of coffee until auto-started software applications enter a state of readiness. In rare cases I witnessed that they even brought in their own laptop and left alone the hardware provided by their employer. How embarrassing this might be... Anyway, latest hardware means faster processing, lower latency and higher productivity of each single employee. Or at least it is plausible to cut off that argument that a job couldn't be done due to hardware-related limitations, etc. blah blah. Oh before I forget to write about that one: Get yourself at least a second screen! Working with multiple screens increases a software developers productivity No matter the screen size, okay at least 19\" should do, put an additional screen on your desk. If you are working on a laptop, use the VGA or HDMI output for the better. In worst case you could use an older machine and install Synergy on both of them. The rise in your productivity using a second screen compared to switching applications is outstanding. And the return of investment (ROI) is incredible fast. I really wonder how archaic certain software companies are when they don't provide a second screen to their employees. Here is a little anecdote from the past: It was on my explicit request that my former employer started to introduce additional screens at work desks after I simply demonstrated to the manager that it is more efficient and productive to work on multiple screens. At that time I was used to work on three screens since years on my private machine at home, and it was absolutely frustrating to go to work. A couple of days later, I got a new graphics card, a second screen and my boss was overly happy with his own dual-screen machine. \uD83D\uDE09 Next, let's talk about software. Once again, stay on top of the game and work with the latest and greatest version that is currently on the market. Not only to show your commitment to your job but also to have access to the state-of-the-art on productivity. Just recently, I worked as consultant for a local company here in Mauritius and honestly I was shocked to see the numerous installations of Visual Studio 2005 on their Windows 7 machines. And to make things even worse, team leaders had either Visual Studio 2008 or 2010 on their machines. Taking into consideration that Visual Studio 2012 was in the final stages to hit the market, I urged them to plan and run a general upgrade on all machines. Eventually, you might ask for the reason. Well, there were several of them and I'm going to discuss them in no preferential order. Cost of licenses True, software licenses do cost money and unfortunately it isn't obvious to some people that you have to take expenses on licenses into your annual budget planning. So no money planned, no upgrades available. Interestingly, while addressing this dilemma to my clients they are not aware of certain subsidizes programmes offered by the software producers. For example, as Registered Microsoft Partner you can purchase tailor-made variations of MSDN at an affordable price. I'm always working on the latest available version of Microsoft Visual Studio, Microsoft SQL Server or Xamarin Studio. And if time allows even on the beta versions... Project circumstances The project is targeted to version X of the operating system and has to work with version Y of that product. Well, software changes and so should your development environment. There have been rare cases that a newer operating system simply sucked but in general go for the latest. Simply to leverage the improvements, read: bug fixes, and the new features that those software packages are offering. And to be prepared just in case that the client is going to ask you: \"Is (y)our software working properly on the upcoming OS?\" It's always better to say something like this \"Yes, it does (with some minor modifications). We already tested it some weeks/months ago while in beta stage.\" than to shrug your shoulders and respond something like that \"We don't know.\" Maintenance of deployed applications Oh yes, the crux with legacy code and the demons from the past. Well, in that case do yourself a big big favour and virtualise the environment. Simple as that, get a copy of either Microsoft Hyper-V, Oracle VirtualBox or VMware Workstation or Player, and move that old-fashioned piece of code over there. I'm going to write more on software virtualisation later in this article. Proper chair Remember what I wrote about touch typing? As a software developer you are likely to sit between six and eight hours every day at your workplace. Okay, there are these 'unpleasant' interruptions to either have a break or to attend a meeting but at the end of the day you are sitting most of the time at your desk. Either to code, to test or to read about your active tasks. Again, I think it is absolutely essential that you have an ergonomic chair that is well-suited for your body. Don't save money on your health! In case that you're employed and not comfortable with your chair or desk, speak to your manager or superior and evaluate possible solutions. It will be in their interest that you are confident and happy to come to work instead of having that annoying tension in your right shoulder or aches in your spine after an intensive coding session. You will be surprised what a difference a good chair can make compared to one of those standard office chairs that you get for cheap. and it doesn't have to be expensive but at least there should be a couple of decent features: Height adjustable Adjustable back shield Support for your lower spine and pelvis Adjustable arm rests Mesh fabrics Well, you might think that I can easily speak of that being self-employed. But hey, no wait a second... even being an employer I took care of the well-being of my employees at their work space. As said, it is crucial that you feel comfortable at work. Comfort = happiness = motivation = results It's that simple! Again, when consulting companies I usually ask them whether they have an initial budget for each new employee or even an annual allowance for the existing ones that could be used for the working environment. Of course, there is none... What would be the benefit of this? Hm, isn't it obvious that if an employee is allowed to 'design and implement' her work desk based on her choices is eventually higher bonded emotionally to this environment and therefore less likely to change later on? No? Virtualisation and isolated project environments This might not be applicable in all cases but nonetheless I think it is worth to mention and discuss it here, too. Frankly speaking, I use virtualisation of software since more than a decade. If I remember correctly, I used to have an early copy of VMware Workstation at that time, and it was quite an experimental experience to run multiple copies of Windows on the same computer. But it was absolutely delightful to be able to work on Windows 2000 and to test the software on Windows 98 or Windows NT 4.0 without the necessity of having another physical machine. Actually, virtualisation software is always one of the first software packages that I install on a new (or re-installed) machine. Furthermore, as soon as you are working on multiple projects or for a number of clients, virtualisation always provides me with a clean development environment. Do you remember the old days of DLL hell? Yes? Well, I don't! \uD83D\uDE09 When I was assigned to work a new project I either asked the responsible project manager to give me a virtual machine, or if not available I created a master image which I put an the company's file server for all my team mates. The purpose of the virtualised environment is simply to avoid any kind of unpredictable behaviour of your development conditions. Furthermore, I am able to design and create the virtual image exactly to the likings of one of the customer machines. Later on, I used to run VMware Converter in order to virtualise one of the client's computer into a virtual one. This way, I was able to develop, run and test the software under more 'real' conditions. And compared to my colleagues I had absolutely no trouble to install patches, upgrades and newer versions of software packages that are involved in the development process of a particular project. If it happened that it didn't work for the better, revert to latest snapshot and tada I'm back on track without any serious delays or whatsoever. Again, this also allows any developer to install and experience new versions of their IDE or additional software packages. Well, at that time it was mainly about incompatibility of different ActiveX controls or versions of Microsoft Office. In case that you have to provide support for older operating systems for your customers. No problem, put that old-faggot in a VM and enjoy the latest OS you can get on your amazing hardware. Virtualise your work environment to ease your life Need to test your latest web project with various browsers on another operating system or older versions of a particular browser, use a virtual machine and be happy! Don't forget that this also shows your expertise and knowledge towards your clients. If it runs on your machine doesn't necessarilty mean that it runs on any machine. So be prepared and test it as much as you can. I recently had the situation that one of my clients had their website designed and implemented by another web designer. Fair enough everything was displayed and working as expected until I started to run my usual cross-OS and cross-browser tests in order to check my modifications in the code behind. You can't imagine the visual differences on various browsers on Mac OS X, Linux, Android or an iOS-based device. 'Unfortunately', it was a real eye-opener for the client and more work for the web designer to improve the site. Ergonomic desk and working environment Source:imgur.com Well, this chapter is related to your proper choice of chair but goes a little bit beyond that. I'm going to leave my point of view on the working desk, on some accessories and constraints on your surrounding. You should pay some attention to your work desk, mainly on the height of the surface which is supposed to align properly with the position of your arms, especially your wrists. In case that you don't have access to a height-adjustable desk, you might be surprised what wonders a couple of bricks can do... Talking about elevation, last year I saw a couple of alternatives to the conventional sitting position. Personally, I was mainly impressed by the concepts of the standing desk and the treadmill desk. The first one comes in quite handy and could be realised easily with only a few additional items whereas the latter might not be the best choice for your surrounding after all. But in case that you are working in your noise-reduced cubicle or even in your own office room. Yes, why not work-out while pouring those challenging software requirements into lines of code and test cases. Next, take care that you spend some bucks on the keyboard, mouse, touch pad or trackball that you are using all day long. Based on my own experience I can tell you to take this dead-serious. At the beginning of my programmer's career I was always wondering about a tensed neck and shoulder. Finally, I found out that this was caused by hand movements on the mouse! And as soon as I replaced the mouse with a trackball I never had any issues of that kind anymore. There are serious, chronically injuries reported for any kind of typing work. Don't ruin your health because you (or rather your employer) saved some bucks on a proper keyboard. It's absolutely not worth it. The problem here is that the symptoms are not visible at the beginning but slowly sneak in after some months or even years of working at the desk. Listen to your body and change your equipment in case of problems. Just give it a try and see how it works. Also, take care that your surrounding is calm and quiet. Put your maximum effort into your working comfort. If there's a problem, address it - ad hoc. Do not ignore it or push it away from you. Again, if you are not happy with your workspace your motivation will start to decay. Treat yourself with a professional environment and also don't be afraid of talking to your manager. It is very simple to describe your show-case and to proof that the circumstance might have a negative impact on your ability to focus, to concentrate and to deliver proper results as expected. A quick and cheap solution to 'dis-connect' yourself from your surrounding is to put on headphones or earplugs. Not only do you block any disturbing sounds but you also visualise to your colleagues a simple yet effective 'Do not disturb'. Again, I can tell you out of my own experience it works incredible well. I used to yell (and sometimes even swear) at my mates when they came to my desk and talked to me out of the blue. Guys, my apologies for that but we found a solution. After I discussed this with my boss, we came up with a simple system that in case of questions or issues to speak about to notify each other through an instant message. It worked flawlessly from day one on, and was quickly adapted by all colleagues. This paragraph might be restricted to offices in Mauritius only but you never know... Comfortable temperature! I cannot recall how many times I went into a freezer instead of an office for software developers here over the island. It is sheer insanity of some companies on how they set the temperature of their air conditioning. As a rule of thumb the inside temperature should not exceed a difference of 5 degrees Celsius compared to the outdoor temperature. Anything else is not healthy and will get you sick. Can you imagine to see computer workers sitting in long-sleeves or even cardigans at their PC while everything else outside is literally melting away and daring for water? No? Then you should come to Mauritius... Clean desk policy Another hot topic that you should pay some attention to are those 'unnecessary extras' on your work desk. Golden rule #1: No food on or near the desk! Seriously, do not keep any kind of food or snacks or whatsoever edible in the reachable surrounding of your work desk. It is not only disgusting to have crumbs of bread between the keys of a keyboard but also unhealthy for your body as you tend to eat mindless and unconsciousness. And leaving your desk to have a drink or to have a snack gives you the ability to chat and exchange with your co-workers, too. Please, feel free and tell me in the comment section about how your desktop currently looks like. If you'd like to, take a picture and send it to me via email. I would be glad to write a follow-up article on this one with your contributions, seriously. Looking at my desk at the moment, I'm not too happy either. And as a matter of fact I just interrupted my work on this article, cleared up my desk and took a picture for you: Keep your desk as clean as possible to avoid any kind of distraction Now, I'm feeling more comfortable and more important: Less distracted! Golden rule #2: Divide your daily tasks into chunks! You don't have to monitor your email inbox every minute, don't need those nasty notification popups, or check your buddies' status on any social network. Instead, just have your instant messaging software up and running minimized, and focus on your main work: Coding. Same as you are off from your desk occasionally, take care of this kind of tasks on specific times or bind them to other activities. For example, my mail client is usually closed during the day and only on certain activities, like ie. going to the tea kitchen to drink some water or to have my lunch break, etc. I check my mails and reply to them immediately. Don't forget, email is actually an asynchronous way of communication and you might assume that your communication partner is not expecting an answer within a couple of minutes. If they do, sorry then the choice of medium is rather poor; in such cases it would be better to use either instant messaging or pick up the phone and call the person. Golden rule #3: Put your mobile away! If your (personal) cellular or your smartphone isn't involved into your development process, simply do yourself a favour and put it out of your sight. Best would be to switch it into 'Meeting' profile and simply leave it in your bag. Please don't forget that you are at work, and not somewhere at a cafe or in a pub. But there might be some kind of emergency case, you might argue. True but honestly, everyone is (or should be) aware of that you are actually at work and that they should call at the office in order to reach you. And let's not forget that the battery could be down, you forgot your mobile at home, etc. So, in case of an emergency it is way safer to call at the office. Access to literature and training Apart from writing software it is absolutely compulsory for any serious software developer to read books, blog articles and technical advisories, to watch tutorials or even to attend conferences on the technology you are working in. Actually, this is common to any kind of profession! Maybe I might be wrong here but for example doctors have to document their annual progress on latest research and products in the medical field. And so should software developers, too. When was the last time that you read a book on a specific technology? Do you know how many software-related books you read during a year? Did you read any book since you left university? Again, these are some of my standard questions when working with a new team of software developers. As for my part, I read approximately 5 to 6 technical titles in one year. Sometimes, I don't even need the content on an active project but the title did attract my attention and therefore I go for it. Interestingly, there had been occasions where that non-related title gave me an advantage on future work. Nowadays, there are endless possibilities to gain access to high-quality material, especially in the field of software development. If you are a Windows developer go and check out MSDN and TechNet. Microsoft's latest achievement is called Microsoft Virtual Academy which provides online material and video courses on specific topics - for free! The recorded material is top-notch and you are able to download it for latter reference. Furthermore, I highly recommend you to get a subscription to one of the many eLearning platforms like Pluralsight, Lynda, TekPub, etc. just to name a few. And not to forget, check out the online tutorials from tool and libraries; ie. Telerik TV is a great resource in case you are working with their products. Pick up the challenge and go for certifications. Yes, certifications will help you massively to know where you are standing with your knowledge, how to isolate blank spots and give you a deeper insight on the technologies you are dealing with on a daily base. Depending on your position, you should classify certifications as an investment into your future and to be able to stand out against other competitors on the market. As an employee you might like to discuss certifications as being part of your annual performance report. Not only do you show your employer clearly that you care for your job but it also increases your value for your employer. And honestly, it is a simple win-win situation for both parties. You get the chance to proof your knowledge and the certifications are personal, and your employer knows that you are able to deliver high quality results at a certain standard. Learning on the job or during your spare time is essential as a software craftsman. Keep yourself educated and valuable to yourself, your employer and your clients. Image credit: JC Gellidon if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'essentials-for-software-developer.html'; this.page.identifier = '9271ed1c97_idessentials-for-software-developer'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "eula-vor-kauf-lesen.html": {
    "href": "eula-vor-kauf-lesen.html",
    "title": "EULA vor Kauf lesen - Get Blogged by JoKi",
    "summary": "EULA vor Kauf lesen Copy Markdown View Markdown GitHub Markdown Source EULA vor Kauf lesen Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'eula-vor-kauf-lesen'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'eula-vor-kauf-lesen'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'EULA vor Kauf lesen') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-13 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Na endlich... \uD83D\uDE09 https://www.microsoft.com/downloads/details.aspx?familyid=0d1e5525-561a-4abb-9f47-b01d05964b63&displaylang=en Ich finde es immer wieder faszinierend, dass die EULA eines Produktes erst gelesen werden kann, wenn man a) Das Paket / die Verpackung geöffnet oder b) das Produkt gerade installiert. Wie soll man denn da noch die EULA ablehnen und das Produkt wieder zurückgeben können? Well, wer VFP 9.0 zurück gibt, sollte sich eh überlegen, seinen Computer zu verkaufen \uD83D\uDE09 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'eula-vor-kauf-lesen.html'; this.page.identifier = '9271ed1c97_ideula-vor-kauf-lesen'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "every-rose-has-its-thorn.html": {
    "href": "every-rose-has-its-thorn.html",
    "title": "Every rose has its thorn - Get Blogged by JoKi",
    "summary": "Every rose has its thorn Copy Markdown View Markdown GitHub Markdown Source Every rose has its thorn Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'every-rose-has-its-thorn'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'every-rose-has-its-thorn'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Every rose has its thorn') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-11 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Im Zuge der Community GetTogethers besteht glücklicherweise die Chance, dass man mit geschätzten Kollegen und Freunden zusammenkommt. Leider verhindert die räumliche Distanz oftmals die gemeinsamen Treffen, nunja, in Zeiten digitaler Kommunikationsmittel und Flatrate für's Telefonieren ist das nicht weiter bedauernswert. Aber... sind wir mal ehrlich, die Offline-Meetings sind doch wesentlich emotionaler und ergreifender. Es war mir eine Herzensfreude endlich mal wieder Tiger (aka Alexander) die Pfote zu schütteln zu dürfen. Same procedure with my good fellow The Icebeer (aka Special Agent G aka Golo). Well, to all other common readers of my blog I apologize to write this paragraph in my poor English tongue but probably there could be misinterpretations... So, please don't take these sentences too serious. I try be to nice^W gentle. Hey, Späßle gemacht! \uD83D\uDE09 Versuchen wir weiter auf deutsch zu schreiben, auch wenn ich damit genauso meine lieben Schwierigkeiten habe... Hm, vielleicht sollte ich mehr auf Fuchsig schreiben, aber dann versteht mich ja keiner... snief So, weiter in der Agenda... Mittagessen! Nein, Quatsch... Selbstverständlich gab's wie bei den vorherigen Community Gatherings supertolle Beiträge und Neuigkeiten für uns und unsere Usergroups, Stammtische, Onlineforen, etc. Ich hab' auf alle Fälle wieder einiges an Informationen mitnehmen können und versuche das im nächsten Announcement für den VFP Stammtisch Speyer wieder korrekt zusammen zu bekommen. Hm, irgendwie kamen wir wieder einmal einige Sachen bekannt vor: Coding4Fun sei nur als ein Stichwort hier genannt. Ich kann nicht beurteilen wer zuerst auf der Szene war \uD83D\uDE09 Siehe Blogroll auf der linken Seite (unterer Bereich...) Hm, ich sehe gerade, dass ich nicht so stylisch bin und das 'for' statt der '4' verwende. Aber... die Kernaussage ist identisch: Programmieren macht Spass und Laune - egal, ob man es als Freizeit, Hobby oder Profession betrachtet. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'every-rose-has-its-thorn.html'; this.page.identifier = '9271ed1c97_idevery-rose-has-its-thorn'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "experiences-on-windows-live-writer---.html": {
    "href": "experiences-on-windows-live-writer---.html",
    "title": "Experiences on Windows Live Writer... - Get Blogged by JoKi",
    "summary": "Experiences on Windows Live Writer... Copy Markdown View Markdown GitHub Markdown Source Experiences on Windows Live Writer... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'experiences-on-windows-live-writer---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'experiences-on-windows-live-writer---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Experiences on Windows Live Writer...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-08-14 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After reading the blog entry of Eric I gave Windows Live Writer a try. Well, the installation is really annoying. Especially the addtional options after the core installation of Writer. What the heck do those funny marketing names stand for? Okay, it's an new toolbar for IE but if I choose not the install Desktop Search engine why is another setup routine asking me that crap again? On Folio? Phishing Filter, etc... sounds nice but in my opinion phishing has primarly to deal with handles and I hardly remember that IE is a mail client - maybe I missed something in the past? Eric already mentioned the gimmick w/ F2 key and setting a time stamp. Funny, it's only a date picker... time, eh? Time doesn't matter... But... why inventing a new product like Windows Live Writer as frontend to Web Services while there is already a Microsoft product on the floor? Ever heard of Microsoft InfoPath - that one is already mentioned to act as frontend to XML/databases. Some kind of light-weight Access to remote backend systems like Web applications (using Web Services API) or SQL Server. And even saving a draft is currently a joke... what about the common dialogs to specify a directory and file name? Actually InfoPath templates as frontend provide more options. I'm missing a feature like 'download existing Weblog' in order to have a local backup. What about comment reading? Currently Live Writer looks like FrontPage for poors. Wow, I found published posts below File :: Open Post... - charming! And I'd like to categorize my posts... Category selection? Huhu? Well, I will test and use this tool during the next couple of weeks, but man there is much work to do... Sincerely, JoKi PS: This is try #4 to post this article. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'experiences-on-windows-live-writer---.html'; this.page.identifier = '9271ed1c97_idexperiences-on-windows-live-writer---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "experimente-mit-bluetooth.html": {
    "href": "experimente-mit-bluetooth.html",
    "title": "Experimente mit BlueTooth - Get Blogged by JoKi",
    "summary": "Experimente mit BlueTooth Copy Markdown View Markdown GitHub Markdown Source Experimente mit BlueTooth Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'experimente-mit-bluetooth'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'experimente-mit-bluetooth'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Experimente mit BlueTooth') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-03 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Endlich habe ich mal die Möglichkeit gefunden mit BlueTooth (BT, neudeutsch: Blauzahn) zu experimentieren. Joah, ganz okay. Aber wirklich nur für kurze Distanzen geeignet. Sozusagen für das kleine Büro oder von Jackentasche bis Ohr oder so... Konkret konnte ich hier mit meinem Asus-Laptop mit integriertem BT und Windows XP im Zusammenspiel mit einem HeadSet (Trust BT210) und dem MDA II experimentieren. Das HeadSet war nach ein paar kleinen Anlaufschwierigkeiten - Lesen will gelernt sein - ziemlich einfach in Betrieb genommen. Nur leider scheint das Teil ein gewaltiges Problem mit der Rückkopplung zu haben, da die Verwendung in Skype so gut wie nicht möglich war. Ständig hat sich die automatische Lautstärkeregelung dazwischen geklemmt. Nunja, vielleicht lag's ja auch einfach nur an mir. Den MDA II konnte ich nach ein paar Probiereieren und Tests dann endlich als Funkmodem über BT nutzen. Um es konkret zu sagen... Dieser Beitrag flog per BT und MDA-Verbindung in die Weiten des Internets. Evtl. mal später noch die Kombination MDA II mit dem HeadSet probieren. Vielleicht ist die Rückkoplung dann geringer. Wer weiß... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'experimente-mit-bluetooth.html'; this.page.identifier = '9271ed1c97_idexperimente-mit-bluetooth'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "exploring-various-sharepoint-blogs.html": {
    "href": "exploring-various-sharepoint-blogs.html",
    "title": "Exploring various SharePoint blogs - Get Blogged by JoKi",
    "summary": "Exploring various SharePoint blogs Copy Markdown View Markdown GitHub Markdown Source Exploring various SharePoint blogs Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'exploring-various-sharepoint-blogs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'exploring-various-sharepoint-blogs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Exploring various SharePoint blogs') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-09-05 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Quick summary on the activities I did yesterday - aka Day 4. Actually, it wasn't too much but I went through a number of articles on various blogs and online forums. Just for general purpose and to see whether my collection is going to have more entries. Well, so far I have to admit that the resources are good. Unfortunately, the blog of Doerfler is a little bit scarce. I went through it completely and there wasn't too much interesting information (yet) but this particular entry is worth mentioning: Wiederherstellen einer gelöschten Websitesammlung (How to restore a deleted site collection) Taking into consideration that there haven't been any new entries since November 2012 I would say that the blog is dormant. It would be great to have new entries in the future... My first (baby) steps in the following SharePoint communities are only scratching the surface, and I'm really looking forward to dig deeper. There seems to be a lot of valuable information available: SharePoint Community SharePointCommunity I think that I already found a true gem over there which is going to give me some extra time: How to begin learning SharePoint (for beginners) - Great overview and link collection Mark Jones. Interestingly, I already discovered a couple of his entries, like the video tutorials by Andrew Connell. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'exploring-various-sharepoint-blogs.html'; this.page.identifier = '9271ed1c97_idexploring-various-sharepoint-blogs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "expression-evaluator-failed-error-67.html": {
    "href": "expression-evaluator-failed-error-67.html",
    "title": "Expression evaluator failed (Error 67) - Get Blogged by JoKi",
    "summary": "Expression evaluator failed (Error 67) Copy Markdown View Markdown GitHub Markdown Source Expression evaluator failed (Error 67) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'expression-evaluator-failed-error-67'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'expression-evaluator-failed-error-67'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Expression evaluator failed (Error 67)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-07 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Immer wieder faszinierend, welche Fehlermeldungen einem über den Weg laufen können: Expression evaluator failed (Error 67) An internal consistency check in the Visual FoxPro expression evaluator failed. This might be caused by a damaged Visual FoxPro object code file. Recompile the program that caused the error. Zum Glück ist die Aufforderung korrekt und nach einem Recompile ist das Problem weg. Nunja, Füchse eben... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'expression-evaluator-failed-error-67.html'; this.page.identifier = '9271ed1c97_idexpression-evaluator-failed-error-67'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "extend-a-virtual-disk-and-its-logical-volume.html": {
    "href": "extend-a-virtual-disk-and-its-logical-volume.html",
    "title": "How to extend a virtual disk and its logical volume(s) in CentOS - Get Blogged by JoKi",
    "summary": "How to extend a virtual disk and its logical volume(s) in CentOS Copy Markdown View Markdown GitHub Markdown Source How to extend a virtual disk and its logical volume(s) in CentOS Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'extend-a-virtual-disk-and-its-logical-volume'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'extend-a-virtual-disk-and-its-logical-volume'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'How to extend a virtual disk and its logical volume(s) in CentOS') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-03-04 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Lately, I ran into the situation that one of my services which is hosted in a virtual machine stopped working. A quick check revealed that the hard disk ran out of disk space and it was about time to increase the available storage. Don't laugh but the system is running on CentOS 5.x with a mere 13 GB virtual disk - well, since years already. And one golden rule I learned from experience: Never touch a running system - kept me away from any modifications. Well, there's always a time that change has more benefits than not touching the system... Running out of disk space That is (now: was) the situation before the following steps. # df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/VolGroup00-LogVol00 12G 12G 0G 100% / /dev/hdc1 99M 36M 59M 38% /boot tmpfs 252M 0 252M 0% /dev/shm And I can tell you that even a Linux OS doesn't like the lack of space on the system drive at all. Backup your data and drives Although, I think it is not necessary to stress the fact that you should (read: have to) backup your data and drives always. Just to be on the safe side, and to revert back to the original state easily and quickly. Preparing the virtual disk First, shut down your virtual machine. Even though it is possible to run almost all of the following in a running environment I doubt that for the casual (home) administrator reading this article it might not be necessary to risk your data. Okay next, it would be necessary to expand the size of virtual hard disk, and then afterwards extend the logical volume with the newly created partition. Converting the disk format from VMDK to VDI Well, the system has kind of a past and it was originally created on VMware Server 1.0, then upgraded to VMware Server 2.0, and some years back I switched over to SUN, eh Oracle, VirtualBox. So, the original virtual disk was still in VMware's format - vmdk. VirtualBox is not able to expand that type of disk drive (yet): $ vboxmanage modifyhd virtual-disk1.vmdk --resize 40960 0%... Progress state: VBOX_E_NOT_SUPPORTED VBoxManage: error: Resize hard disk operation for this format is not implemented yet! In order to complete this initial task it was necessary to convert the format from VMDK to VDI. This is done by cloning the disk into the new format: $ vboxmanage clonehd virtual-disk1.vmdk virtual-disk1.vdi --format VDI 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100% Clone hard disk created in format 'vdi'. UUID: 37ef6965-0000-4159-861a-d1c64d9c060f Depending on the actual disk size and your system performance this might take some time. Remain patient and let the system do the job. Meanwhile, you might check your mails or post your intentions on various social media networks. Just kidding! \uD83D\uDE09 Note: Additionally to your backup, it might be interesting to keep an archive of the original VMDK file. Just in case... Resizing the virtual drive Now, that we have our drive in VDI format we are able to expand the disk size. Let's try our previous statement again, but this time with the newly created VDI disk: $ vboxmanage modifyhd virtual-disk1.vdi --resize 40960 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100% After successful expansion it is about time to check our disk modifications in the settings of the virtual machine. Launch the Oracle VM VirtualBox Manager, select your virtual machine and either press the Settings button in the toolbar or choose from the menu Machine -- Settings (Ctrl+S). Next, select the Storage entry from the side pane and then the virtual hard disk that we just modified. You should see something similar to the following screenshot. Oracle VM VirtualBox: Settings of virtual disk size and proper assignment In case that you had to convert an VMDK drive to VDI, please select the newly cloned drive from the dropdown list in the Attributes area. You should have the .VDI disk attached to your virtual machine. Pro tip: In case that you want to keep the original disk type in VMDK format, do a complete round-trip and clone the extended drive back to VMDK format, like so: $ vboxmanage clonehd virtual-disk1.vdi virtual-disk1.vmdk --format VMDK 0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100% Clone hard disk created in format 'vmdk'. UUID: 37ef6965-0000-4159-861a-d1c64d9c060f Using VMware Player The above mentioned conversion is not necessary in case that you have an installation of VMware Player or even VMware Workstation at hand. In my case, I didn't. Anyway, VMware Player gives you the ability to expand the disk capacity through the UI directly. Open the Virtual Machine Settings, then select the Hard Disk from the list of devices and below the Disk information you'll have a list of utilities in the dropdown list. Choose \"Expand...\" in order to change the disk's capacity. VMware Player: Virtual Machine Settings and ability to expand the disk capacity That's all for the 'physical' expansion of our hard drive. Now, start your virtual machine as we are going to take of the software part. Preparing the partition The system in our virtual machine is still out of space and we are going to change this now. Please follow the next steps closely. Accidentally, I trapped myself and had to revert a couple of steps because I didn't pay enough attention to the details. I'll come back to that at the end of this article. Okay, we enlarged the physical drive, and now we have to take care of that unallocated space. Let's check the partition table first like so: $ sudo fdisk -l Disk /dev/hdc: 42.9 GB, 42949672960 bytes 255 heads, 63 sectors/track, 5221 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Device Boot Start End Blocks Id System /dev/hdc1 * 1 13 104391 83 Linux /dev/hdc2 14 1827 14570955 8e Linux LVM This will give us an overview of the existing partitions. The new disk size has been accepted already and it's now time to create a new partition. Creating a new partition This article is not about how to use fdisk on the command line. There are other tutorials with more detailed information about the particular steps, and you might have a look at the manual page of fdisk directly. To create a new partition we launch fdisk on our hard drive. # fdisk /dev/hdc Then we create a new full-size partition at the end of the disk drive with the following sequence of keystrokes: n - new partition p - primary partition 3 - next partition number [Enter] - confirm first cylinder [Enter] - confirm last cylinder t - change partition's type or system id 3 - choose newly created partition id 8e - specify hex code of system type (Linux LVM) w - write partition table to disk Of course, this might vary depending on your existing partition table. Please, adapt your inputs accordingly. After completing all steps your hard drive might have a similar output like this one: # fdisk -l Disk /dev/hdc: 42.9 GB, 42949672960 bytes 255 heads, 63 sectors/track, 5221 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Device Boot Start End Blocks Id System /dev/hdc1 * 1 13 104391 83 Linux /dev/hdc2 14 1827 14570955 8e Linux LVM /dev/hdc3 1828 5221 27262305 8e Linux LVM Alternative: Extend an existing partition During my research in preparation of this 'surgery' I also came across a couple of resources online where it is stated that it is also possible to enlarge an existing partition to the full extend of the new capacity of the disk drive. The general recommendation is to use a more sophisticated partitioning tool like the freely available GParted. While using a Windows operating system, I clearly would recommend this approach, too. Mainly because it's your system drive that runs out of space and Windows is hardly capable to span over multiple drives (at least in casual environments like home and office desktops). I already did this in the past to enlarge a Windows Server 2008 R2 system, and it went as smooth as expected. You can download the GParted LiveCD ISO here. Expanding the logical volume Once you understood the necessary steps to extend an existing logical volume it's fairly easy to reproduce. The general concept is well documented in Chapter 4 of the LVM Administrator's Guide for CentOS, and is based on the following procedure. First, you create a physical volume (PV) based on your new partition which is then extended into an existing volume group (VG), and then finally extended into a logical volume (LV) within a volume group. After resizing the logical volume you are ready to go and the new disk space is available in your system. Creating the physical volume This will prepare the partition for use in a volume group. # pvcreate /dev/hdc3 Physical volume \"/dev/hdc3\" successfully created Note: Depending on whether you are going to extend your system with an additional partition or a whole drive, you might even skip the partitioning and create a physical volume using the whole hard disk. Extending the volume group Prior to any extension is not too bad to get an overview of the existing volume groups. This can be done like so: # vgdisplay --- Volume group --- VG Name VolGroup00 System ID ... With the information of the available name of the volume group, we are now able to extend it with our physical partition: # vgextend VolGroup00 /dev/hdc3 Volume group \"VolGroup00\" successfully extended Done. Next step is to assign the available space to the logical volume that we would like to expand. Extending the logical volume, finally Same procedure here, it's advised to have an overview of the existing logical volume first: # lvdisplay --- Logical volume --- LV Name /dev/VolGroup00/LogVol00 VG Name VolGroup00 ... --- Logical volume --- LV Name /dev/VolGroup00/LogVol01 VG Name VolGroup00 ... In my case, I'm going to extend the first logical volume with the new physical volume. # lvextend /dev/VolGroup00/LogVol00 /dev/hdc3 Extending logical volume root to 37.94 GB Logical volume LogVol00 successfully resized Almost done. After expanding the volume group and the logical volume we only have to populate the information about the new total size of our logical volume. # resize2fs /dev/VolGroup00/LogVol00 That's it! Just to complete the whole process check the available disk space: # df -h Filesystem Size Used Avail Use% Mounted on /dev/mapper/VolGroup00-LogVol00 37G 12G 24G 32% / /dev/hdc1 99M 36M 59M 38% /boot tmpfs 252M 0 252M 0% /dev/shm Okay, my system has now more than triple the original disk size. Let's see how many years it is going to run before I might have to add another partition to the logical volume. Summary Once you understood the necessary steps on how to expand a logical volume it's actually very simple to reproduce. You start with the physical preparation of the hard disk, either by adding a new drive or by expanding an existing virtual drive. Then you create a partition of type 8e - Linux LVM - for that new unallocated drive space. And to complete the procedure you have to walk through the LVM handling by creating the physical volume, extending the volume group and finally extending and resizing the logical volume in that volume group. Even in case of an error it's relatively simple to track down the root cause and take care of it. Beware of the details... Troubleshooting As mentioned earlier I got stuck in the process because of two issues. First, my Linux operating system, here CentOS 5.3, didn't get the new partition table automatically. So when I run the following command I got the response that the specified device isn't available. # pvcreate /dev/hdc3 Device /dev/hdc3 not found (or ignored by filtering). A quick look on the internet revealed that this could happen and the modified partition table can be updated manually like so: # partprobe -s /dev/hdc: msdos partitions 1 2 3 # partx -a /dev/hdc Or to keep things simple, just reboot the system. But that would be too easy and a waste of time... And second, I mistyped the name of the logical volume - LogVol01 instead of LogVol00 - and added the new partition to the swap area. Well, following are some details on how to detect that kind of issue and how to remove a physical drive from a logical volume and volume group. I came to an abrupt stop on the very last step, resizing the file system with the newly allocated volume: # resize2fs /dev/VolGroup00/LogVol01 resize2fs: bad magic number in super-block while trying to open /dev/VolGroup00/LogVol01 couldn’t find valid filesystem superblock That's kind of bummer. Especially, so close to the finish line. Well, it turned out that volume group had two logical volumes LogVol00 and LogVol01. And the second one is actually used for the swap area. Which easily explains why there is absolutely no valid filesystem superblock to be found - no matter how hard I would try it. First, I thought that it might have been a problem with the filesystem type, as I am using ext2, ext3, ext4, and xfs interchangeable. But a quick check confirmed that I'm using the right command. In case of xfs you might have to work with xfs_grow command instead of resize2fs. Then I checked the overview output of my logical volumes again and I was kind of lucky that I discovered that I increased the wrong volume: # lvdisplay --- Logical volume --- LV Name /dev/VolGroup00/LogVol01 VG Name VolGroup00 ... LV Size 24.83 GB Current LE 891 Segments 2 The original output stated a LV Size of 1.91 GB. Due to the nature of LVM I couldn't remove the physical hard drive because it is reported to be in use. Therefore, it is first necessary to decrease the volume size to its original value like so: # lvreduce -L 1.9G /dev/VolGroup00/LogVol01 Rounding up size to full physical extent 1.91 GB Eventually, you might have to move all allocated space off the physical volume to other free physical volumes in the same volume group like so: # pvmove /dev/hdc3 Then it's possible to remove (or better said to reduce) the drive from the volume group like so: # vgreduce VolGroup00 /dev/hdc3 And then to start over again by adding the fresh physical volume back to the volume group again and then assigning it to the proper logical volume, like so: # pvcreate /dev/hdc3 # vgextend VolGroup00 /dev/hdc3 # lvextend /dev/VolGroup00/LogVol00 /dev/hdc3 # resize2fs /dev/VolGroup00/LogVol00 Now, everything went smooth and my last system check of the logical volumes looks like so: # lvdisplay --- Logical volume --- LV Name /dev/VolGroup00/LogVol00 VG Name VolGroup00 ... LV Size 37.94 GB Current LE 1214 Segments 2 --- Logical volume --- LV Name /dev/VolGroup00/LogVol01 VG Name VolGroup00 ... LV Size 1.91 GB Current LE 61 Segments 1 As usual, follow administrator's rule number 1: Don't Panic! Have a look at the history of your inputs, here your commands on the console, and check them entirely with the outputs of the various display commands - pvdisplay to show information about physical volumes, vgdisplay for details about volume groups, and finally lvdisplay to get an overview of your logical volumes. Good luck with your mission and leave your comments below. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'extend-a-virtual-disk-and-its-logical-volume.html'; this.page.identifier = '9271ed1c97_idextend-a-virtual-disk-and-its-logical-volume'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "family-affairs.html": {
    "href": "family-affairs.html",
    "title": "Family affairs - Get Blogged by JoKi",
    "summary": "Family affairs Copy Markdown View Markdown GitHub Markdown Source Family affairs Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'family-affairs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'family-affairs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Family affairs') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-10-08 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Since a couple of days part of my family is here in Mauritius for holidays. Well, they stay for about four weeks and based on the fact that our next generation keeps us quite busy, it is really welcoming to have some members of the family around. It is also quite interesting to see their reactions after roughly two weeks and that they already fear the near end of their stay. Well, I guess that are already 'infected by the Mauritius fever'... Who knows. Anyways, it will not be their last time here. We are already 'booked' for December and January by another sister and her boyfriend for their second visit... Well, well, it's good to see that both families get to know each other. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'family-affairs.html'; this.page.identifier = '9271ed1c97_idfamily-affairs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "family-website-aktualisiert.html": {
    "href": "family-website-aktualisiert.html",
    "title": "Family Website aktualisiert - Get Blogged by JoKi",
    "summary": "Family Website aktualisiert Copy Markdown View Markdown GitHub Markdown Source Family Website aktualisiert Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'family-website-aktualisiert'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'family-website-aktualisiert'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Family Website aktualisiert') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-21 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nur eine Kleinigkeit in persönlicher Sache... Muss auch mal sein. Vielleicht haben einige Leser bereits entdeckt, dass dieses Blog auf einer Subdomain - https://jochen.kirstaetter.name/ - basiert. Und vielleicht hat sich in diesem Zuge dessen wiederum ein kleiner Prozentsatz gefragt, was sich denn unter der 'Hauptdomain' verbergen könnte... vielleicht wohlgemerkt. \uD83E\uDD2A Nun, um das Rätsel zu lösen: Auf der Domain https://kirstaetter.name/ bzw. https://www.kirstaetter.name/ befindet sich eine persönliche Site der Familie Kirstätter. Dort gibt's ab und zu mal Informationen aus Marienthal. Weiterhin dient diese Adresse als zentrale Anlaufstelle für mich und andere Familienmitglieder zur Verzweigung; wie etwa zu diesem Blog. In Analogie zu einigen Bekannten, etwa Alex, Thomas, Martin oder Golo werde ich ebenfalls in den nächsten Wochen meine persönliche Website neben diesem Blog um einige Einträge erweitern. Dies bedingt sich unter anderem durch die nahe Zukunft mit der Aussicht auf Arbeit im Ausland, aber auch weil mir die anderen Sites einfach gefallen. Sozusagen die persönliche Visitenkarte im Internet eben. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'family-website-aktualisiert.html'; this.page.identifier = '9271ed1c97_idfamily-website-aktualisiert'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "fckeditor-in-active-foxpro-pages-verwenden.html": {
    "href": "fckeditor-in-active-foxpro-pages-verwenden.html",
    "title": "FCKeditor in Active FoxPro Pages verwenden - Get Blogged by JoKi",
    "summary": "FCKeditor in Active FoxPro Pages verwenden Copy Markdown View Markdown GitHub Markdown Source FCKeditor in Active FoxPro Pages verwenden Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fckeditor-in-active-foxpro-pages-verwenden'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fckeditor-in-active-foxpro-pages-verwenden'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'FCKeditor in Active FoxPro Pages verwenden') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-11 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Download und AFP Beispiele Dank der Bereitstellung der Klassendefinition und Beispiele von Sönke Freitag ist die Integration des FCKeditor in Active FoxPro Pages eines wahres Heimspiel. Das Beispiel selbst ist als AFP Applikation (*.afpa) aufgesetzt und zeigt wie man in einem Dokument den FCKeditor integriert und anwendet, während im zweiten Beispiel die Verarbeitung des erstellten Text in einer Übersicht gelistet wird. Absolut ausreichend für den ersten Einstieg in die Materie. Es war lediglich eine kleine Anpassung durchzuführen: Die Pfadkorrektur auf den Basispfad für die eigentliche Funktionalität des FCKeditors. Ich führe dies jedoch auf meine atypische Speicherung des Archivs zurück. Was nun? Motiviert durch diesen positiven ersten Eindruck habe ich mir ein bisschen den Quellcode angesehen und siehe da - eine Klassendefinition und einen kleineren Codeblock - und fertig. Nun, mangelnde Beispiele habe ich sicherlich keine und mein 'erstes Opfer' ist in den meisten Fällen das AfpWiki. Der aktuelle Editor kann sicherlich ein Face-Lifting gebrauchen, also wieso nicht FCKeditor. \uD83D\uDE09 Integration in AfpWiki Gedacht, getan... Hm, es sind noch kleinere Anpassungen am Beispielcode von Sönke durchzuführen, damit es wirklich reibungslos klappt. Keine Sorge, ich werde die Patches direkt an ihn per Mail schicken. In der Hoffnung, dass mit dem nächsten Release des Editors auch die veränderten AFP-Dokumente dabei sind. Meine erste Empfehlung ist, dass man die VFP Klassendefinition in eine eigene Prozedurendatei umsiedelt und diese neue Datei wiederum per INCLUDE-Statement in die eigene, bestehende AFP Anwendung integriert: \\*!<[INCLUDE: \"fck.code\"]> Ich verwende hier exemplarisch die Endung .code, da es in der AFP so üblich für Prozedurendateien ist. Es spricht aber auch überhaupt nichts dagegen, dass man die VFP übliche Endung .prg verwendet. Das ist lediglich eine persönliche Geschmackssache, der AFP ist es gleichgültig. Nach der Integration der Klassendefinition werden die beiden Beispieldokumente (*.afp) ins AfpWiki assimiliert: <% sBasePath=\"./FckEditor/\" && Change this to your local path lcText=[This is some <strong>sample text</strong>. You are using ] lcText=lcText+[<a href='https://www.fckeditor.net/'>FCKeditor</a>.] oFCKeditor = CREATEOBJECT(\"FCKeditor\") oFCKeditor.fckeditor(\"FCKeditor1\") oFCKeditor.BasePath = sBasePath oFCKeditor.cValue = lcText Response.Write( oFCKeditor.Create() ) %> Nach einer anfänglichen Wehr - wer kommt auch auf die Idee, Variablennamen gleich Feldnamen des aktiven Alias zu setzen, und diesen auch noch 'html' zu nennen? - präsentierte sich der FCKeditor im gewohnten Bild im Layout des AfpWiki. Soviel zum Thema AFP debuggen und Master Pages \uD83D\uDE09 Optisches Finetuning Die weiteren Schritte umfassen nun lediglich noch das Customizing und Skinning für die Optik des AfpWiki, welche aber individuell jede/r selbst abstimmen kann. Weiterhin werde ich in der applikationsweiten Konfiguration afpwiki.afpa noch ein paar Kontroloptionen für die Bearbeitung von Topics im Allgemeinen und dann editorspezifisch im Detail einrichten. Schliesslich habe ich nur wenig Interesse, dass beim Upgrade des FCKeditors mein eigener Code entsorgt werden würde. Fazit Zusammenfassend lässt sich sagen, dass die mitgelieferten AFP-Beispiele des FCKeditors mit einer eventuellen Anpassung des Basispfades direkt funktionieren und die Integration in eigene AFP-Anwendungen wirklich nur eine Sache von Minuten ist. Viel Spass bei der Verwendung des FCKeditors und bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'fckeditor-in-active-foxpro-pages-verwenden.html'; this.page.identifier = '9271ed1c97_idfckeditor-in-active-foxpro-pages-verwenden'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "feedback-on-lugm-meeting-15-06-2013.html": {
    "href": "feedback-on-lugm-meeting-15-06-2013.html",
    "title": "Feedback on meeting of the Linux User Group of Mauritius - Get Blogged by JoKi",
    "summary": "Feedback on meeting of the Linux User Group of Mauritius Copy Markdown View Markdown GitHub Markdown Source Feedback on meeting of the Linux User Group of Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'feedback-on-lugm-meeting-15-06-2013'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'feedback-on-lugm-meeting-15-06-2013'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Feedback on meeting of the Linux User Group of Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-06-17 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Once upon a time in a country far far away... Okay, actually it's not that bad but it has been a while since the last meeting of the Linux User Group of Mauritius (LUGM). There have been plans in the past but it never really happened. Finally, Selven took the opportunity and organised a new meetup with low administrative overhead, proper scheduling on alternative dates and a small attendee's survey on the preferred option. All the pre-work was nicely executed. First, I wasn't sure whether it would be possible to attend. Luckily I got some additional information, like children should come, too, and I was sold to this community gathering. According to other long-term members of the LUGM it was the first time 'ever' that a gathering was organised outside of Quatre Bornes, and I have to admit it was great! LUGM - user group meeting on the 15.06.2013 in L'Escalier Quick overview of Linux & the LUGM With a little bit of delay the LUGM meeting officially started with a quick overview and introduction to Linux presented by Avinash. During the session he told the audience that there had been quite some activity over the island some years ago but unfortunately it had been quiet during recent times. Of course, we also spoke about the acknowledged world dominance of Linux - thanks to Android - and the interesting possibilities for countries like Mauritius. It is known that a couple of public institutions have there back-end infrastructure running on Red Hat Linux systems but the presence on the desktop is still very low. Users are simply hanging on to Windows XP and older versions of Microsoft Office. Following the introduction of the LUGM Ajay joined into the session and it quickly changed into a panel discussion with lots of interesting questions and answers, sharing of first-hand experience either on the job or in private use of Linux, and a couple of ideas about how the LUGM could promote Linux a bit more in Mauritius. It was great to get an insight into other attendee's opinion and activities. Especially taking into consideration that I'm already using Linux since around 1996/97. Frankly speaking, I bought a SuSE 4.x distribution back in those days because I couldn't achieve certain tasks on Windows NT 4.0 without spending a fortune. OpenELEC Mediacenter Next, Selven gave us decent introduction on OpenELEC: Open Embedded Linux Entertainment Center (OpenELEC) is a small Linux distribution built from scratch as a platform to turn your computer into an XBMC media center. OpenELEC is designed to make your system boot fast, and the install is so easy that anyone can turn a blank PC into a media machine in less than 15 minutes. I didn't know about it until this presentation. In the past, I was mainly attached to Video Disk Recorder (VDR) as it allows the use of satellite receiver cards very easily. Hm, somehow I'm still missing my precious HTPC that I had to leave back in Germany years ago. It was great piece of hardware and software; self-built PC in a standard HiFi-sized (43cm) black desktop casing with 2 full-featured Hauppauge DVB-s cards, an old-fashioned Voodoo graphics card, WiFi card, Pioneer slot-in DVD drive, and fully remote controlled via infra-red thanks to Debian, VDR and LIRC. With EP Guide, scheduled recordings and general multimedia centre it offered all the necessary comfort in the living room, besides a Nintendo game console; actually a GameCube at that time... But I have to admit that putting OpenELEC on a Raspberry Pi would be a cool DIY project in the near future. LUGM - our next generation of linux users (15.06.2013) Project Evil Genius (PEG) Don't be scared of the paragraph header. Ish gave us a cool explanation why he named it PEG - Project Evil Genius; it's because of the time of the day when he was scripting down his ideas to be able to build, package and provide software applications to various Linux distributions. The main influence came from openSuSE but the platform didn't cater for his needs and ideas, so he started to work out something on his own. During his passionate session he also talked about the amazing experience he had due to other Linux users from all over the world. During the next couple of days Ish promised to put his script to GitHub... Looking forward to that. Check out Ish's personal blog over at hacklog.in. Highly recommended to read. Why India? Simply because the registration fees per year for an Indian domain are approximately 20 times less than for a Mauritian domain (.mu). Exploring the beach of L'Escalier af the meeting 'After-party' at the beach of L'Escalier Puh, after such interesting sessions, ideas around Linux and good conversation during the breaks and over lunch it was time for a little break-out. Selven suggested that we all should head down to the beach of L'Escalier and get some impressions of nature down here in the south of the island. Talking about 'beach' \uD83D\uDE09 - absolutely not comparable to the white-sanded ones here in Flic en Flac... There are no lagoons down at the south coast of Mauriitus, and watching the breaking waves is a different experience and joy after all. Unfortunately, I was a little bit worried about the thoughtless littering at such a remote location. You have to drive on natural paths through the sugar cane fields and I was really shocked by the amount of rubbish lying around almost everywhere. Sad, really sad and it concurs with Yasir's recent article on the same topic. Resumé & outlook It was a great event. I met with new people, had some good conversations, and even my children enjoyed themselves the whole day. The location was well-chosen, enough space for each and everyone, parking spaces and even a playground for the children. Also, a big \"Thank You\" to Selven and his helpers for the organisation and preparation of lunch. I'm kind of sure that this was an exceptional meeting of LUGM and I'm really looking forward to the next gathering of Linux geeks. Hopefully, soon. All images are courtesy of Avinash Meetoo. More pictures are available on Flickr. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'feedback-on-lugm-meeting-15-06-2013.html'; this.page.identifier = '9271ed1c97_idfeedback-on-lugm-meeting-15-06-2013'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "feedback-on-meeting-of-the-mscc-20072013.html": {
    "href": "feedback-on-meeting-of-the-mscc-20072013.html",
    "title": "Feedback on meeting of the MSCC - 20.07.2013 - Get Blogged by JoKi",
    "summary": "Feedback on meeting of the MSCC - 20.07.2013 Copy Markdown View Markdown GitHub Markdown Source Feedback on meeting of the MSCC - 20.07.2013 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'feedback-on-meeting-of-the-mscc-20072013'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'feedback-on-meeting-of-the-mscc-20072013'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Feedback on meeting of the MSCC - 20.07.2013') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-07-23 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Impression of our meetup on 20.07.2013 Low quantity but high quality! Meetup summary: Quick introduction to ‪MSCC‬ and interesting topics in general, especially for freshman from/at university. It also seems that the open concept of OUYA (Android-based gaming console) got some attention and hopefully new fans. More info is available online: https://www.ouya.tv/ Design contest The design contest is still going on... There's currently only one submission. Come on, you web & graphic designers in Mauritius - SHOW YOUR WORK Any draft will be published over here: MSCC Design Contest - https://www.facebook.com/media/set/?set=a.200036533488751.1073741829.181737551985316&type=3 Goodies give-away And the first 2 one-month subscriptions for Pluralsight have been well received by attendees, too. Unfortunately, we didn't have any free WiFi at Talking Drums - so, we might have to consider another location for the next meetup. Change of schedule As we spoke about the advantages of gathering during the weekend, we worked out a schedule that could be applied to future meetups of the MSCC. I'm going to address this tomorrow during our regular meetings on Wednesday to see about the response of other members, too. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'feedback-on-meeting-of-the-mscc-20072013.html'; this.page.identifier = '9271ed1c97_idfeedback-on-meeting-of-the-mscc-20072013'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "finally----afp-faq-wieder-online.html": {
    "href": "finally----afp-faq-wieder-online.html",
    "title": "Finally... AFP FAQ wieder online - Get Blogged by JoKi",
    "summary": "Finally... AFP FAQ wieder online Copy Markdown View Markdown GitHub Markdown Source Finally... AFP FAQ wieder online Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally----afp-faq-wieder-online'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally----afp-faq-wieder-online'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Finally... AFP FAQ wieder online') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-13 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Puuuuuh, was ein Alptraum... ...nach etlichen Stunden ist der Artikelbestand der AFP FAQ beinahe vollständig wiederhergestellt - ca. 300 Artikel auf deutsch, englisch und spanisch. Ist wirklich spannend zu sehen, wieviel und vorallem wo man seine Spuren im Internet hinterlässt. Wirklich faszinierend und diesem speziellen Fall ein wahrer Segen. Die AFP FAQ ist seit Montag Abend auf einem Windows Server mit Active FoxPro Pages der ProLib Software GmbH deponiert. Vielen Dank an meine Kollegen für diese Übergangslösung. Und dieses Mal wurde reinen Tisch gemacht. Ich hatte zwar schon ein paar Überlegungen die AFP-Programmierung der FAQ umzustellen, aber es hätte ja nicht auf diese Radikaltour hinauslaufen müssen... Well, shit happens but we're back on track. Effektiv ist der komplette Unterbau der AFP FAQ vollständig ausgetauscht! Und ich bin wirklich froh darüber, dass ich nicht nur die AFP FAQ programmiert und geschrieben habe, sondern ebenfalls das AfpWiki - einer Wikiengine auf Basis der Active FoxPro Pages. Nun, statt einer parallelen Entwicklung von FAQ und Wiki habe ich mich am Sonntag dazu entschlossen, dass die beiden einen Merge erfahren: Die Technik des AfpWiki und die Artikel der AFP FAQ... \uD83D\uDE0E Gerade weil ich nicht mehr an das 'Rohmaterial' der FAQ-Artikel kam, sondern nur noch an die HTML-Ergebnisse, war diese Lösung die schnellste und effizienteste. Und, wenn ich ehrlich bin, auch die faulste Art und Weise des Restore. Ich hatte zwar noch die AFP-Programmierung als lokale Arbeitskopie, aber nur wenig Interesse den HTML-Code zu zerpflücken und wieder in die Datenbank zu schieben. Okay, in eine Datenbank musste es eh wieder, aber durch die Verwendung des AfpWiki konnte ich die ganzen Artikel zunächst einmal per Copy&Paste direkt reinwerfen. Unter Berücksichtigung der Sprache und Beibehaltung der ursprünglichen \"Artikelnummer\" konnte ich auch leicht überprüfen wieviel inzwischen restauriert ist. Neben meinen eigenen Recherchen habe ich auch Zuspruch, Tipps und gespeicherte Browsercaches aus der Community erhalten! DANKE! In den nächsten Tagen werden noch einige Aufräumarbeiten stattfinden, aber die Erreichbarkeit ist wiederhergestellt. Und das ist doch eigentlich das wichtigste, oder? Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'finally----afp-faq-wieder-online.html'; this.page.identifier = '9271ed1c97_idfinally----afp-faq-wieder-online'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "finally-tweeting.html": {
    "href": "finally-tweeting.html",
    "title": "Finally tweeting - Get Blogged by JoKi",
    "summary": "Finally tweeting Copy Markdown View Markdown GitHub Markdown Source Finally tweeting Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally-tweeting'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally-tweeting'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Finally tweeting') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-02-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Finally, I could convince myself to register an account on Twitter to start micro-blogging. The registration is free, easy and straight forward. No woes at all... I could have set up a tweet already ages ago but honestly I have to say that I never had any real motivation about this whole concept. Even now, I am not 100% sure about the pros and benefits of Twitter are. Maybe I will get some feedback on this during the next weeks. Perhaps I might use this micro-blogging space for some more questions or even rants... Let's see. \uD83D\uDE09 Anyways, the \"Get in contact\" area in the sidebar is updated and you might follow my tweet on this URL: https://www.x.com/JKirstaetter Sadly, my alias \"JoKi\" is alerady in use by someone else, but that's life: First come, first serve. One of the next things I am going to do is to check out of one the many Codeplex projects, namely VFPTweetAPI by Olaf Doschke. First of all, it is to get more familiar with Twitter itself and second I hope that it is easier to tweet from VFP directly than to launch my favorite browser. And last but not least, I might integrate the component into one of my projects like Sunshine. Definetily more on this topic in the future... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'finally-tweeting.html'; this.page.identifier = '9271ed1c97_idfinally-tweeting'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "finally-upgrade-from-nokia-x3-to-samsung-galaxy-s-iii.html": {
    "href": "finally-upgrade-from-nokia-x3-to-samsung-galaxy-s-iii.html",
    "title": "Finally, upgrade from Nokia X3 to Samsung Galaxy S III - Get Blogged by JoKi",
    "summary": "Finally, upgrade from Nokia X3 to Samsung Galaxy S III Copy Markdown View Markdown GitHub Markdown Source Finally, upgrade from Nokia X3 to Samsung Galaxy S III Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally-upgrade-from-nokia-x3-to-samsung-galaxy-s-iii'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally-upgrade-from-nokia-x3-to-samsung-galaxy-s-iii'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Finally, upgrade from Nokia X3 to Samsung Galaxy S III') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-10-13 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); This time, something slightly different but nonetheless not less interesting, hopefully. Living on a remote island like Mauritius, ill-praised 'Cyber Island' in the Indian Ocean, has its advantages in life style and relaxed environment to life in but in terms of technological aspects it can be quite a nightmare. Well, I guess this might be different story to report about... one day. Cyber Island Mauritius Despite it's shiny advertisement as Cyber Island and business in ICT hub to Africa, Mauritius is not on the latest track of available models in computer hardware or, in the context of this article, cellulars or smart-phone, or communication technology in general. Okay, I have to admit that this statement is only partly true. Money can buy, even here in Mauritius. Luckily, there are ways and ways to deal with this outcry of modern, read: technological, civilisation issues. Online shopping you might think? Yes, for sure, until you discover in your checkout procedure that a small island in the Indian Ocean isn't a preferred destination for delivery and the precious time you spent on putting your items into your cart and feeding your personal level of anticipation gets ruined on the last stint. Ordering from abroad saves you money Anyway, I got in touch with my personal courier and luckily there were some extra-kilos left in the luggage. First obstacle sorted, we have a Transporter! Okay, on the next occasion off to Amazon online and using their Prime service for fast delivery. Actually, the order was placed on Saturday evening and everything got delivered on Tuesday morning - nice job in less than 72 hours. Okay, among the items of that shopping rush I ordered a shiny Samsung Galaxy S III 16GB in oceanic blue - did I mention, that you hardly get a blue model in Mauritius? - for my BWE. Interesting side-notes: First, Amazon Germany dropped the prices for roughly 30% on the S3, and we got the 16GB model for less than 500 Euro (or approx. Rs. 19.500,-) compared to the usual Rs. 27.000,- on the local market. It even varies whether the local price is inclusive or exclusive VAT (15%). Second, since a while she was bothering me to get an iPhone and an iPad for her, fair enough I thought, decent hardware, posh design and reliable services. Until we watched the 'magical' introduction of Samsung's new models at the IFA exhibition, she read the bashing comments on Google+ on the iPhone 5 and I gave her a brief summary on the law suit between Apple and Samsung in the USA. So, yes, Samsung USA is right, the next big thing is already here - literally. My BWE loves the look and touch of the Galaxy S3. And for me it was more cost-effective in terms of purchases done at the App Store, ups, Play Store. Transfer of contacts, text messages and media files Okay, now that the hardware is in place, how to transfer all those contacts, text messages, media files, etc. between those two devices? In the past, I used to use the Nokia Communication Suite between various models but now for Android? Well, as usual Google and Bing are reliable friends and among the first hits I came across an article about How to Transfer Contacts from Nokia to Android. Couldn't be easier, right? Well, sort of... my main Windows systems are already running on Windows 8, and this actually caused problems with the mobile/smart-phone device drivers. The article provides the download for an older version 1.10 which upgrades to 2.11 (as time of writing this entry) but both couldn't get the Galaxy S3 and the Nokia connected. Shame on me... the product page clearly doesn't mention Windows 8 (for now) and Windows 8 isn't available for the general audience at all... After I took a spare machine running on Windows Vista everything went smooth. Software installed, upgrade done, device drivers for Android automatically downloaded and installed, and the same painless routine for the Nokia part. I think, I rebooted the system twice during the whole setup procedure but hey, it was more or less a distraction while coding some stuff in ASP.NET MVC and Telerik Kendo UI. The transfer of contacts and text messages was done via Wondershare MobileGo for Android, and all media files by moving the additional microSD card from one device to the other. But even without an external SD card, it would have been very easy to copy the files via Windows Explorer directly. Little catch and excellent service Fine, we are almost done and the only step left is to shift the SIM card... Ouch, gotcha! The X3 uses a standard size SIM card while the S III only accepts microSIM form factor. What an irony, bigger smartphone needs smaller SIM card. Luckily, the next showroom of Emtel is just 5 mins away up the road, and the service staff over there know their job. Finally, after roughly 10 mins of paper work, activation and small chit-chat, the S3 came to life on the mobile network. Owning a smart-phone now and knowing that my BWE would like to interact more on social networks away from home, especially to upload pictures and provide local 'check-ins', I activated a data package for her in advance, too. Even that it is Saturday, everything was already done and ready to be used. Nice bonus: The Emtel clerk directly offered me to set up the configuration for the Emtel data services, yes sure, go ahead, this saves me to search for that in the settings. Okay, spoiler-alert here, setting a static APN to access the Emtel network and the internet wouldn't be a challenge. But hey, she already had the phone in her hands and I could keep my eyes on the children. Well done, Emtel! Resume Thanks to the useful software package by Wondershare is was a hands-free experience to transfer all the data from a Nokia mobile on Symbian S60 to a Samsung Galaxy S III on Android Ice Cream Sandwich (ICS). In the future, this wont be a serious issue at all anymore thanks to synchronisation services and cloud storage. And for now, I'm only waiting for the official upgrades for Jelly Bean. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'finally-upgrade-from-nokia-x3-to-samsung-galaxy-s-iii.html'; this.page.identifier = '9271ed1c97_idfinally-upgrade-from-nokia-x3-to-samsung-galaxy-s-iii'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "finally.html": {
    "href": "finally.html",
    "title": "Finally - Get Blogged by JoKi",
    "summary": "Finally Copy Markdown View Markdown GitHub Markdown Source Finally Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'finally'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Finally') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-11-14 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); TILT! Also, so eine Konferenz kann wirklich an den Leistungen zehren. Aktuell sitze ich nach einer Schlafphase von mehr als 12 Stunden wieder einigermaßen entspannt und erholt vor der Kiste, um ein paar Überarbeitungen an meinen Slides zu machen. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'finally.html'; this.page.identifier = '9271ed1c97_idfinally'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "firefox-1-5-beta.html": {
    "href": "firefox-1-5-beta.html",
    "title": "Firefox 1.5 Beta - Get Blogged by JoKi",
    "summary": "Firefox 1.5 Beta Copy Markdown View Markdown GitHub Markdown Source Firefox 1.5 Beta Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'firefox-1-5-beta'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'firefox-1-5-beta'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Firefox 1.5 Beta') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-10 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hm, also, ich kann mir kein richtiges Bild von der kommenden Version des Firefox machen, aber da sind noch einige suspekte Aspekte. Neben der Tatsache, dass die Software Achterbahn mit der CPU-Last fährt, habe ich bisher noch keinen nativen Support für Scalable Vector Graphics (SVG) ausmachen können. Okay, es haben sich auch viele Dinge verbessert, aber die für mich interessanten und relevanten Features haben mich bisher doch enttäuscht. Meine erste Überraschung brachte der Options-Dialog - Öhem, welchen Sinn macht die Leiste oben statt an der linken Seite? Nicht wirklich einleuchtend, aber mal was anderes. Inzwischen habe ich mir die Sache noch ein paar angesehen und muss sagen, dass ich der Thematik - nämlich Icons statt Tabs - so langsam etwas abgewinnen kann. Wahrscheinlich liegt's aber auch daran, dass wir im laufenden Projekt ebenfalls verstärkt mit Icons bzw. Hyperlinks anstelle von Pageframes mit Tabs verwenden. Who knows... Weiterhin gefallen mir die Änderungen der Connection- und Proxyeinstellungen besser als in Firefox 1.0.x. Weiteres nettes Feature im Toolsmenu ist die Anzeige, dass neue Mails in Thunderbird vorliegen - Nun, da ich auch besonders häufig ins Toolsmenu schaue, bringt mir das unheimlich viel... hüstel - Leute, wieso gibt's keinen Indikator mit Tooltip in der Statusleiste des Browsers? Also ehrlich, da muss definitiv noch was dran gemacht werden. Nettes Gimmick sind die Favicons in den Bookmarks - wirklich nett. Zumindest erhöht es den visuellen Wiedererkennungseffekt. Apropos Bookmarks... eines der wichtigsten Bookmarks ist IMHO die Startseite, oder? Nun, wieso Startseite und nicht Startseiten? Und hier legt der Feuerfuchs wieder einmal die Messlatte höher! Denn als 'Startseite' können durch Verwendung des Pipe-Zeichen multiple URLs angegeben werden, welche dann direkt als Tabs geöffnet werden. \uD83D\uDE0E Aber... Wann kommt endlich die 'Linknavigation-Leiste' in den Firefox? Sorry, aber gerade wenn man mit den -Tags sinnvoll arbeitet, dann vermisst man dieses Feature - welches Mozilla und Opera haben - doch mehr als schmerzlich. Naja, warten wir mal die nächsten Versionen ab. Insgesamt halte ich die aktuellen Betas für ausgereift und freue mich auf das finale Release des Firefox 1.5 (oder wie der Browser auch genannt werden mag.) Bis denne, JoKi Update: Inzwischen konnte ich mir einige SVG-Grafiken ansehen. Besucht am besten mal https://www.svgbasics.com - da gibt's einiges zu lernen. Und ich konnte eine Ursache für die zyklischen CPU-Lasten ausmachen: Mein Blog... Naja, Web Site Report ging bereits an das Firefox-Team raus. Mal abwarten, ob da Resonanz kommen wird. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'firefox-1-5-beta.html'; this.page.identifier = '9271ed1c97_idfirefox-1-5-beta'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "first-steps-using-dictation-in-windows-10.html": {
    "href": "first-steps-using-dictation-in-windows-10.html",
    "title": "First steps using dictation in Windows 10 - Get Blogged by JoKi",
    "summary": "First steps using dictation in Windows 10 Copy Markdown View Markdown GitHub Markdown Source First steps using dictation in Windows 10 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'first-steps-using-dictation-in-windows-10'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'first-steps-using-dictation-in-windows-10'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'First steps using dictation in Windows 10') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-01-07 - Filed under Windows (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Let's try to do an experiment using dictation mode in Windows 10 to write a whole article. Dictation mode in Windows 10 can be launched instantly hitting the Win + H hotkey. This opens a small information bar at the of your screen and enables active listening to your microphone. Eventually, you might have to allow permissions. Currently, dictation in Windows 10 seems to be limited to certain languages, i.e. English (United States). Other languages like German or English (United Kingdom) might be available in the near future. Although dictation mode sounds very easy to use; it's actually not. It looks pretty simple when you see it in movies or watching a practitioner that dictates something into a recording device. Trying to write or better said to dictate an article the first time requires a lot more concentration than typing. The challenge here is to clear up your mind and then articulate the whole sentence or paragraph in one go. I assume, that with a little bit more practice it becomes more natural and fluent. Right now, it's really hard for me. Story editor in Ghost Desktop I'm currently trying to figure out whether there are special commands and expressions that would help to improve the experience. My first impression about dictation mode is that it seems to be a bit clumsy. Perhaps this is related to using the editor in Ghost Desktop instead of a regular text box. I'm going to explore dictation mode in other applications like Microsoft Word, too. Surprisingly, when the system understands you the results are OK and usable. I guess, that it boils down to little bit more training regarding speed and proper pronunciation. At the current stage there seems to be a number of glitches. For example, dictating more than one sentence seems to have an issue. Commonly used expressions like 'to', 'too' and the number 'two' cannot be differentiated. Sometimes words are completely misinterpreted. Key words like 'Comma' or 'Period' are quite often misunderstood. Dictation mode also notifies me that the current app, meaning the story editor in Ghost Desktop, is not fully supported at this stage. Another observation is that dictation mode seems to rely on a proper Internet connectivity in order to work as expected. OneNote Using OneNote seems to be a better option to use dictation mode properly. The flow of information while you speak is OK. It is better than the story editor in Ghost Desktop. It is interesting to observe progress in OneNote compared to other applications. In OneNote it seems to be a constant evaluation of the spoken word whereas in Ghost it takes the whole sentence. Also, it is amazing to see that it works over multiple sentences. Special commands or common expressions like 'Comma' and 'Period' are correctly interpreted as ',' and as '.' respectively. It should be noted that OneNote offers its own Dictate mode. For this test I'm using the Windows 10 dictation mode only. Screen recording: First steps using Windows 10 dictation mode in OneNote (UWP) \"I'm using the built-in Game Capture feature of Windows 10 to record this dictation. The interesting aspect here seems to be that everything is going out to the Internet. It is quite impressive to see your own words written on to the screen. Although, it is really hard to articulate a sentence in your head first and then to speak it out loud, it takes some time and training to improve over time. Overall the dictation mode in OneNote is better compared to the story editor in Ghost Desktop. More tests should be conducted soon. Trying out more commands like Spelling mode: m o z 5 w Spelling mode clearly requires more training on my side to know the international alphabet by heart. Also there are several commands available but it is not clear at the moment which can be used in which ones not to operate properly. Dictation mode gives you visual feedback whether a command is currently supported or not. Unfortunately it is not visible in this screen recording.\" Word Online Instead of using the desktop application of Microsoft Word I thought that I give the online edition a try first. So far, it seems that it works similar to OneNote. For the first attempt I'm using Firefox on Windows, later on I will try Microsoft Edge on the same document. Notepad A quick test with Microsoft Notepad did not succeed. It seems that the scenarios to use dictation mode are limited at the moment. Which seems kind of strange because other online references showed an earlier version of Dictation used in Notepad. Okay, it took me several attempts to successfully dictate some sentences into Notepad. It was mainly to stop and launch Dictation several times hitting Win + H hotkey. Patience seems to be key... Tweetdeck An interesting use case for dictation could be tweets. They are short and concise, meaning relatively ease to articulate and to get them out in one attempt. The street has been dictated. I'm currently running some tests Using dictation mode in Windows 10. It's actually pretty fun. More to come soon. #WindowsInsiders PS: unmodified text... — Jochen Kirstätter ハッカー (@JKirstaetter) January 6, 2019 Dictated my first tweet of 2019 I'm using Tweetdeck as my main client and posting my first dictated tweet of 2019 went surprisingly well, inclusive punctuation and hashtag. Going to try this more often now. Auxiliary noise or ambient music Most of the time while working on this article I was listening to music. Different styles depending on mood and hour of day, as well as via loudspeakers from the laptop or using my headset. Either way, given the volume wasn't too high on the speakers, there had been no implications or drawbacks while dictating my sentences. Also, having a noisy fan or the TV set running did not lower the results. Of course, your mileage might vary depending on your environment. For me it worked as good as being in a complete quiet room. Dictation Mode - It works... This entire article has been dictated as much as possible without manual interaction. Editing and additional resources like images, hyperlinks, and formatting have been added manually afterwards. Due to the issues noted with the story editor of Ghost Desktop the remainder of this article was dictated in OneNote. My overall impression and experience with dictation mode so far is positive. I'm going to research more information on the Internet about compatible applications that are capable to work with Windows 10 dictation mode. I still haven't figured out how to re-activate Listening other then to click on the microphone icon in the dictation bar at the top of the screen. If any reader knows about a hotkey, please leave a comment for me and others. According to the official documentation there is none. The suggested solution is to re-initialize dictation pressing Win + H twice. However there are still some rough edges that need some TLC and polishing. I'm looking forward to evaluate the updates to come and will continue to work with the dictation in Windows 10. Image credit: Jason Rosewell if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'first-steps-using-dictation-in-windows-10.html'; this.page.identifier = '9271ed1c97_idfirst-steps-using-dictation-in-windows-10'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "fkalien-alarm-funstuff.html": {
    "href": "fkalien-alarm-funstuff.html",
    "title": "Fäkalien-Alarm - Funstuff - Get Blogged by JoKi",
    "summary": "Fäkalien-Alarm - Funstuff Copy Markdown View Markdown GitHub Markdown Source Fäkalien-Alarm - Funstuff Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fkalien-alarm-funstuff'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fkalien-alarm-funstuff'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Fäkalien-Alarm - Funstuff') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-06-13 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Oh Mann, in den letzten Tagen ist das Niveau wieder ein wenig gesunken. Aber Spass hat man dabei dennoch: Was tierisch in Analogie zu den Pferden: https://www.insanepictures.com/flash/fartingdogs.swf Und, hey, was soll's, es ist natürlich: https://www.couchkartoffelsalat.de/HP/flash/whg.htm Have fun, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'fkalien-alarm-funstuff.html'; this.page.identifier = '9271ed1c97_idfkalien-alarm-funstuff'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "flash-prsentation-zu-vfp-sp2-ctp.html": {
    "href": "flash-prsentation-zu-vfp-sp2-ctp.html",
    "title": "Flash-Präsentation zu VFP SP2 CTP - Get Blogged by JoKi",
    "summary": "Flash-Präsentation zu VFP SP2 CTP Copy Markdown View Markdown GitHub Markdown Source Flash-Präsentation zu VFP SP2 CTP Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'flash-prsentation-zu-vfp-sp2-ctp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'flash-prsentation-zu-vfp-sp2-ctp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Flash-Präsentation zu VFP SP2 CTP') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-31 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Da sowohl beim letzten Treffen unseres monatlichen VFP-Stammtisch in Speyer als auch im Forum der dFPUG kam die Frage aus, was denn das Service Pack 2 für Visual FoxPro 9.0 zu bieten hätte? Nun, mangels aktueller BugFix-Liste kann man da aktuell leider nur wenig sagen. Was jedoch auf der Download-Seite bei Microsoft genannt wird, ist der Umstand, dass das Reporting weitere Verbesserungen erhalten hat. Und die haben es in sich!. Glücklicherweise ist unsere VFP-Community sehr aktiv in den Belangen des Produkts und daher möchte ich einfach auf eine Demonstration von Lisa Nicolls-Slater aufmerksam machen. Sie ist unter anderem auch für das Reporting System in VFP 9.0 mitverantwortlich gewesen und wenn sie es nicht wissen sollte, wer dann? \uD83E\uDD2A SP2 Demo Mirror von Craig Bailey Die Performance des Videos ist ein 'wenig hakelig', aber die Infos sind 'absolutely great!' Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'flash-prsentation-zu-vfp-sp2-ctp.html'; this.page.identifier = '9271ed1c97_idflash-prsentation-zu-vfp-sp2-ctp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "flexible-work-style-meet-your-new-office.html": {
    "href": "flexible-work-style-meet-your-new-office.html",
    "title": "Flexible Work style, meet your new Office - Get Blogged by JoKi",
    "summary": "Flexible Work style, meet your new Office Copy Markdown View Markdown GitHub Markdown Source Flexible Work style, meet your new Office Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'flexible-work-style-meet-your-new-office'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'flexible-work-style-meet-your-new-office'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Flexible Work style, meet your new Office') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-03-14 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Cyber Island Mauritius is getting more information on the latest technologies provided by Microsoft. Finally... \uD83D\uDE09 After attending \"Driving Business Forward\" by Knowledge 7 it was time to wrap my head around some off-line information on latest Windows platforms. Not that I am in lack of information or software products but simply for the exchange of experience and networking. Windows 8 & Windows Server 2012 In corporation between Harel Mallac Technologies, MSL and Microsoft IOI I had the chance to attend a meeting at the Hennesy Park Hotel at Ebene to get a first hand experience on latest software applications like Windows 8, Windows Server 2012 and Office 2013. Mainly driven by the chance to win an Acer Iconia W520 - no, just kidding! - I was really eager to see some of the stuff in action. Well, I'm already using Windows 8 since the early previews from last year, experimenting quite a bit with Hyper-V in Windows Server 2012 as well as having a proper installation of Office Professional Plus 2013 on my development machines. But you never know... It's always nice to have new points of view and exchange some experiences. Plus, such events are always a great way for networking. Which is actually my main reason to attend. So, it was at no surprise to see familiar faces from the Microsoft Open Doors event that was organised back in October 2012 at Pailles. Kudos to the live demonstrations Despite the loud audio penetration I had a great afternoon, very interesting demonstrations and good insight questions and answers from the team at HMT. Thanks to Siddick and his team members - well done! And remember: If it breaks, blame it on Emtel! \uD83D\uDE09 One of the real interesting aspects was that they run the demos live! Just using an Emtel Home & Office router and Emtel 3G/UMTS connection they showed us the real experience of speed and delay times while administering Windows Server 2012 running in the cloud, okay at the local Emtel data centre, and accessing either App-V or VDI for the real desktop experience on any device. Consumerisation of IT is the buzz word nowadays. To my opinion it was a also a great pleasure to have this kind of interactive session with Siddick. He had a number of very good questions to the audience and vice versa. A couple of them, especially when related to either iOS devices or Android smartphones and tablets were even discussed between various attendees. Goodies, goodies and even more goodies As said earlier, there was great communication between the speakers and the audience. And I guess it was also very pleasant for some of us that there were goodies given out for either excellent questions or right answers. Personally, I had great fun this afternoon, and was very honoured to drive home with some extra goodies. I was given a nice leather etui for a laptop, unfortunately it's too big for a Surface, iPad and Samsung Galaxy Tab but hey, it's great for an Ultrabook, and... tadaa... a Microsoft LifeCam HD-3000. Thanks again to the event sponsors! Hopefully, more on Office 2013 and Office 365 soon Yes, even though that Microsoft's Distribution Account Manager Virginie announced the general availability of Office 2013 Home & Student in Mauritius as FPP, I'm actually more interested in upcoming events around Office Professional 2013 and Office 365. Which is hopefully giving some more sense to 'Cyber Island' soon. More to come, so stay tuned... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'flexible-work-style-meet-your-new-office.html'; this.page.identifier = '9271ed1c97_idflexible-work-style-meet-your-new-office'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "found-by-google.html": {
    "href": "found-by-google.html",
    "title": "Found by Google - Get Blogged by JoKi",
    "summary": "Found by Google Copy Markdown View Markdown GitHub Markdown Source Found by Google Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'found-by-google'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'found-by-google'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Found by Google') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-10 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Die Website ist ohne Frames erstellt, der Content eingepflegt und der Launch offiziell angekündigt, aber... Was dann? Wie findet man nun eine Site? Wie bekommt man Traffic auf die Domain? Well, ein Eintrag in Suchmaschinen muss her. Und nicht nur, in einer oder zwei, sondern in einigen... Richtig? - Meiner Meinung nach, nein. Denn IMHO ist es ausreichend, wenn man sich primär um eine Suchmaschine kümmert: Google. Und da gibt's ein bisschen was zu beachten, aber gleich mehr dazu. Ich möchte lediglich ein Live-Beispiel anführen: https://www.google.de/search?q=FoxPro https://www.google.de/search?hl=de&q=FoxPro&btnG=Suche&meta=lr%3Dlang _de https://www.google.de/search?hl=de&q=FoxPro&btnG=Suche&meta=cr%3Dcoun tryDE Es handelt sich dreimal um die gleiche Suchabfrage - nämlich \"FoxPro\" - mit unterschiedlichen Ausprägungen im Web, im deutschsprachigen Raum und aus Deutschland. Wie kommt man zu einem solchen Ergebnis? - Nun, die Frage ist nicht einfach zu beantworten, dennoch möchte ich meine Erfahrung dazu ein wenig offenlegen. Zu allererst ist es wichtig, dass man die MetaTags der HTML-Dokumente korrekt und ausreichend ausfüllt: meta name=\"description\" content=\"...\" meta name=\"abstract\" content=\"...\"> meta name=\"keywords\" content=\"...\"> meta name=\"modified\" content=\"Wed, 01 Dec 2004 09:44:27 GMT+2\" Dabei sind natürlich Begriffe zu wählen, die zum einen den Kernbereich des Inhalts treffen und zum anderen keinesfalls auf verwandte Begriffe verzichten (diese sind fast wichtiger)... Gut, dann natürlich eine Datei namens 'robots.txt' im Root der Site anlegen und befüllen: https://www.searchengineworld.com/robots/robots_tutorial.htm User-agent: * Disallow: /.svn/ Disallow: /awstats/ Disallow: /images/ Disallow: /staff/ Disallow: /svn/ Disallow: /websvn/ Achja, ein beliebter Fehler ist es, sogenannten 'nicht sichtbaren Text' in den Dokumenten zu verwenden - also schwarz auf schwarz - lasst es... Das wird erkannt und verursacht negative Faktoren. Das sind mal die grundlegenden Aspekte, damit bietet man Suchmaschinen ausreichend Basisnahrung liefert, so dass eine korrekte Indizierung der Site erfolgen kann. Aber... Das erklärt nicht, wie man auf die höheren Ergebnisränke kommt. Und nun kommt ein kleiner Kniff, den ich nachweisbar erfolgreich anwenden konnte und anwende: Die Manipulation des HTTP-Headers der Response in Bezug auf die Datumswerte der Dokumente. Hier mal ein Beispiel (auszugsweise): Date: Sun, 10 Apr 2005 08:27:28 GMT Expires: Mon, 11 Apr 2005 09:27:29 GMT X-Powered-By: AFP/3.0.579 Last-Modified: Wed, 01 Dec 2004 09:44:27 GMT Das Expire der Seite liegt (hier 25 Std in der Zukunft), was wiederum den Suchmaschinen einen Anlass gibt - \"Hmm, da könnte ich mal wieder reinschauen...\". Außerdem halte ich den Zeitpunkt der letzten Änderung ebenfalls für sehr wichtig, wenn nicht sogar als den wichtigsten Aspekt einer Site. Denn mal ehrlich, was interessiert einen selbst, wenn man eine Suchmaschine benutzt? - Aktuelle Informationen oder Ressourcen zu einem bestimmten Thema. Ergo, mit einem zeitnahen Last Modifier bietet man aktuelle Informationen an, und die Suchmaschine \"Hey, die Site ist aktuell, da muss sicherlich was Neues stehen.\" nimmt die Site entsprechend auf. Und das ist IMHO alles, was es benötigt mittel- bis langfristig sehr gute Rankings in Google und darüber in anderen Suchmaschinen zu erhalten. Von sogenannten Massensubskriptionen zu schiess-mich-tot-wievielen Suchdiensten halte ich persönlich überhaupt nichts, sondern sehe darin sogar ein Problem, das zu negativen Ergebnissen führen könnte. Wäre mal cool, zu erfahren, wie eure bisherigen Erfahrungen zu Google sind und ob meine Tipps euch in 2,3 Monaten weiterhelfen konnten. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'found-by-google.html'; this.page.identifier = '9271ed1c97_idfound-by-google'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "fox-is-anywhere.html": {
    "href": "fox-is-anywhere.html",
    "title": "Fox Is Anywhere! - Get Blogged by JoKi",
    "summary": "Fox Is Anywhere! Copy Markdown View Markdown GitHub Markdown Source Fox Is Anywhere! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fox-is-anywhere'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fox-is-anywhere'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Fox Is Anywhere!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-20 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Da soll noch einmal einer sagen, dass VFP keinen Einsatzzweck hätte... Himalayan Database Absolut coole Sache! Vor allem unter der Prämisse, dass die App sauber von CD unter Windows und Mac läuft. Ein klarer Indikator, dass es sich um eine VFP 3.0 Anwendung handeln dürfte. Fox Is Anywhere! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'fox-is-anywhere.html'; this.page.identifier = '9271ed1c97_idfox-is-anywhere'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "foxpro---das-urzeitmonster-aus-der-tiefe---.html": {
    "href": "foxpro---das-urzeitmonster-aus-der-tiefe---.html",
    "title": "FoxPro - das Urzeitmonster aus der Tiefe... - Get Blogged by JoKi",
    "summary": "FoxPro - das Urzeitmonster aus der Tiefe... Copy Markdown View Markdown GitHub Markdown Source FoxPro - das Urzeitmonster aus der Tiefe... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'foxpro---das-urzeitmonster-aus-der-tiefe---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'foxpro---das-urzeitmonster-aus-der-tiefe---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'FoxPro - das Urzeitmonster aus der Tiefe...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-07-16 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Irgendwie trifft der Spruch den Nagel auf den Kopf. FoxPro oder in aktueller Version unter der Bezeichnung Visual FoxPro 9.0 Public Beta in aller Munde ist unter den Sprachen ein modernes Urgestein der Programmierung. Und vielleicht lasse sich Ähnlichkeiten zu Godzilla finden... Nein, wollen wir mal den Spass außen vor lassen. Denn auch heutzutage ist es immer wieder erschreckend zu sehen, wie lange sich ein in die Welt gesetztes Gerücht doch in der IT-Welt hält und wie faszinierend es ist, daß die ganzen Floskel immer wieder auf's Neue voll ins Schwarze treffen. Was gab es nicht alles für Nettigkeiten und dennoch trotz Visual FoxPro auf sehr faszinierende Art und Weise allen Verleumdungen. Nicht etwa mit leeren Sprüchen sondern einzig und allein mit überzeugenden, schlagfertigen Beweisen in Form von Performance, Stabilität und wie es sich für eine 4GL Programmiersprache gehört schneller Anwendungsentwicklung oder auf neudt. Rapid Application Development (RAD). DAS sind die unwiderlegbaren Grundsteine von Visual FoxPro gegenüber vergleichbaren Programmiersprachen - sofern es denn etwas Vergleichbares überhaupt gibt. Denn welche Programmiersprachen hat von Haus folgende Komponenten an Bord? Objektorientierte Programmiersprache als Kernstück Integrated Development Environment (IDE) Datenbank Reportgenerator - der zudem noch distributable ist XML und COM Fähigkeiten Hohe Stabilität Alle Addons der IDE liegen im Quellcode bei Mag es auch Alternativen en mass geben, KEINE ist so komplett wie Visual FoxPro. Leider ist das vielleicht auch das riesige Problem. Denn wer braucht schon alle Komponenten gleichzeitig in seiner Anwendung? Nee, da werden lieber die abgespeckten Varianten genommen oder man kauft sich über Drittanbieter weitere Tools weil es die eigene Programmiersprache einfach nicht anbietet. Nunja, gerade auch unter der Betrachtung der Umstellung bei vielen Visual Basic Entwicklern hoffe ich doch, daß darunter auch einige dabei sind, und einfach in den vollständigen Produktumfang ihres Visual Studio 6 schauen und sich mit dem Gedanken anfreunden könnten, in Zukunft Visual FoxPro zu verwenden. Achja, es könnte so einfach sein... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'foxpro---das-urzeitmonster-aus-der-tiefe---.html'; this.page.identifier = '9271ed1c97_idfoxpro---das-urzeitmonster-aus-der-tiefe---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "foxpro-als-embedded-db-in--net.html": {
    "href": "foxpro-als-embedded-db-in--net.html",
    "title": "FoxPro als embedded DB in .NET - Get Blogged by JoKi",
    "summary": "FoxPro als embedded DB in .NET Copy Markdown View Markdown GitHub Markdown Source FoxPro als embedded DB in .NET Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'foxpro-als-embedded-db-in--net'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'foxpro-als-embedded-db-in--net'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'FoxPro als embedded DB in .NET') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-23 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hallo, ein wenig spät, aber ich würde hier gerne ein paar Ungenauigkeiten klarstellen wollen. Original von Quest Ich würde gerne FoxPro statt Access als DB nutzen, welches ich mit meiner Anwendung gleich mitinstalliere... Hat zur Nutzung von FoxPro mit C# evtl. jemand eine gute Seite die auf ein paar Sachen tiefer eingeht? Interessieren würde mich v.a. Multiuser-Zugriff, Triggers, Views, Stored Proecdures, etc... VFP anstelle von Access zu verwenden, ist auf alle Fälle schon eine interessante Variante. Entsprechendes lässt sich auch der FAQ zu VFP bei Microsoft nachlesen: https://msdn.com/vfoxpro/ Ach ja und was ich noch fragen wollte: Der Kern des Ganzen ist kostenlos und für Visual FoxPro muss ich pro Lizenz zahlen? Hab ich das richtig verstanden? Naja aber durchs MSDN-Abo hab ich schonmal eine Lizenz hier... \uD83D\uDE04 Du musst lediglich für eine Entwicklungsumgebung bezahlen. Der weitere Vertrieb deiner Anwendung ist dir überlassen. Weiterhin möchte ich hierzu sagen, dass der Ole DB Provider für VFP ebenfalls online frei verfügbar ist und du damit eigentlich alles hast, um VFP als Datenbank zu verwenden. In meinem Blog findest du übrigens ein fertiges Beispiel mit allem Drum und Dran. Sogar die Erstellung der Datenbank mit den Tabellen kannst über ADOX und ADO.NET erledigen. So, damit wäre deine initiale Frage beantwortet, schauen wir uns die weiteren Statements an und kommentieren das ein wenig... Original von citizen.ron keine lizenzprobleme und gehen tut mehr als foxpro sich je träumen lassen wird Welche Lizenzprobleme hat man denn mit VFP? Genau das ist doch der Haken, dass Microsoft das Produkt in den letzten Jahren sehr stiefmütterlich betrachtet hat. Und gerade in Bezug auf Verwendung mit ADO.NET ist es erst recht kostenfrei: https://msdn.com/vfoxpro/ dort gibt's den Ole DB Provider und das war's dann auch schon. Mehr ist nicht notwendig. Original von Der Eisbär Vor allem würde ich nicht mehr auf eine Datenbank setzen, die nicht mehr weiterentwickelt wird ... Ansonsten sehe ich das wie citizen.ron ... die Hochzeit von VFP ist vorbei (habe selbst beruflich VFP entwickelt, weiß also durchaus, wovon ich rede \uD83D\uDE09) und der SQL Server kann definitiv deutlich mehr als VFP jemals kann. Zunächst einmal ist VFP ein Produkt, welches seit annähernd 20 Jahren besteht und damit die entsprechende Reife aufzuweisen vermag. Der Produktsupport seitens Microsoft läuft irgendwann 2014 aus. Bis dahin läuft sehr viel Wasser den Rhein hinunter. Weiterhin finde ich es übrigens sehr spannend, dass gerade die Entwicklung von LINQ speziell auf die Features von VFP eingeht und sich daran orientieren wird. Oder wie erklärst du die Integration von Rushmore in .NET 3.0? Über deine Kenntnisse in Bezug auf VFP möchte hier nicht weiteres zum Besten geben, außer dass du kaum mit der eigentlichen Datenbank in Kontakt gekommen bist, da wir im Projekt primär mit dem MS SQL Server (bedingt durch Anforderung des Kunden) arbeiten. Ergo, bezieht sich dein \"Wissen\" primär auf die Programmiersprache denn auf die Datenbank. Original von Der Eisbär VFP ist IMHO unter anderem deshalb schon aus dem Rennen, weil es nicht mehr weiterentwickelt wird. Wo bitte steht dieses Statement? Microsoft hat keine Aussage dazu getroffen. Es wird derzeit an Sedna programmiert, welches als Addon zu VFP 9.0 derzeit angesehen wird. Ob daraus VFP 10 wird oder nicht, entscheidet sich frühestens Mitte/Ende 2007. Original von Der Eisbär Aus VFP kannst Du derzeit mehr schlecht als recht auf .NET zugreifen. Das ändert sich mit Sedna, da Sedna als Integrationsschicht zu verstehen ist. In diesem Sinne sehe ich für VFP-Entwickler keinen Sinn, mit Sedna auf .NET zuzugreifen, da der Overhead massivst ist und man ja doch das .NET Framework benötigt. Und was hat das mit der Frage in Bezug auf die reine VFP-Datenbank zu tun? Ehrlich gesagt, sehe ich hier das Dilemma, dass du VFP immer als Ganzes betrachtest und entsprechend bewertest. Aber eigentlich entspricht VFP 3 Produkten: Programmiersprache, Datenbank und Reportsystem. Was meinst du wie glücklich irgendwelche Office-Anwender sind, wenn man denen einen VFP-COM-Server zur Verfügung stellt, so dass sie stressfrei im Vergleich zu ADO auf ihre Datenquellen kommen... Achja, der Zugriff auf .NET funktioniert genauso wie bei anderen Nicht-.NET-Sprachen: per COM und das klappt gut. Selbst WinForms lassen sich per COM starten und nutzen. So, Schnitt. Der Beitrag kommt wahrscheinlich ziemlich spät, aber ändert nichts an den Kommentaren. Ich möchte hier keine Lanze für VFP als lokale DB-Engine für .NET brechen, sondern es kommt auf die jeweiligen Anforderungen an das System an. Sicherlich lässt sich das gleiche Problem auch mit SQLite, Firebird und anderen RDBMS lösen. Als VFP-Coder tendiere ich selbstredend für VFP als Datenbank, schliesslich sind wir alle subjektiv in unseren Aussagen. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'foxpro-als-embedded-db-in--net.html'; this.page.identifier = '9271ed1c97_idfoxpro-als-embedded-db-in--net'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "foxtales-behind-the-scenes-at-fox-software-by-kerry-nietz.html": {
    "href": "foxtales-behind-the-scenes-at-fox-software-by-kerry-nietz.html",
    "title": "FoxTales: Behind the Scenes at Fox Software by Kerry Nietz - Get Blogged by JoKi",
    "summary": "FoxTales: Behind the Scenes at Fox Software by Kerry Nietz Copy Markdown View Markdown GitHub Markdown Source FoxTales: Behind the Scenes at Fox Software by Kerry Nietz Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'foxtales-behind-the-scenes-at-fox-software-by-kerry-nietz'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'foxtales-behind-the-scenes-at-fox-software-by-kerry-nietz'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'FoxTales: Behind the Scenes at Fox Software by Kerry Nietz') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-06-10 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Flash backs from the past! It's truly amazing to discover that software development from freshman to senior level as well as project management hasn't changed that much. Kerry Nietz describes his memoir from his final year at college to his first job at Fox Software to 'an early retirement' at Microsoft. This title also brought his other fictional novels to my attention. Once again here is the review I published on Amazon: Built to last! I have been around in software development for more than a decade now but honestly I have to admit it is only now that I took the opportunity to read about the history of my used to be primary programming language. In fact, I started with Visual FoxPro 6 back in 1999 and went only down to FoxPro for Windows 2.6 during migration projects - long after the stories described in this title. It is really interesting to see how they actually managed to create a great product with such a small team of developers. \"Create the best Report Writer in the world, out of only sawdust, bubblegum, and dreams.\" - That's the best sentence I'm going to quote from this title in the future. An inspiration to achieve the impossible, only by taking small steps. Just begin the journey - one step after the next one. If you fall, stand up and continue to walk. Kerry takes the reader on an amazing trip through almost 4 years working at a small software company in Perrysburg, Ohio. That went from a another 'look-alike' of the mighty Ashton-Tate dBase to the leading force in database development, long before Microsoft Access (project name: Cirrus) was even finished. It survived Borland Paradox and even nowadays Visual FoxPro is still in daily use in thousands of companies world-wide. Actually, I'm glad that I had the chance to foster my programming knowledge with Visual FoxPro. After his excellent work in software development, Kerry went for a second career as a writer. I'm looking forward to read his other titles soon: Updates Read my review about 'A Star Curiously Singing' by Kerry Nietz. Image generated by Microsoft Designer using the following prompt: \"an illustrated forest opening depicting a fox.\" if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'foxtales-behind-the-scenes-at-fox-software-by-kerry-nietz.html'; this.page.identifier = '9271ed1c97_idfoxtales-behind-the-scenes-at-fox-software-by-kerry-nietz'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "frankfurter-buchmesse.html": {
    "href": "frankfurter-buchmesse.html",
    "title": "Frankfurter Buchmesse - Get Blogged by JoKi",
    "summary": "Frankfurter Buchmesse Copy Markdown View Markdown GitHub Markdown Source Frankfurter Buchmesse Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'frankfurter-buchmesse'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'frankfurter-buchmesse'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Frankfurter Buchmesse') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-07 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es geht weiter mit Terminen und Aussichten: Frankfurter Buchmesse 2005 Vom 19. - 23. Oktober 2005 wird Frankfurt erneut der Schauplatz der größten Messe der Welt für Buch, Multimedia und Kommunikation sein. Als Gastland präsentiert sich Korea. Alles, was Rang und Namen in der Branche hat, trifft sich bei uns: Autoren und Verleger, Buchhändler und Bibliothekare, Kunsthändler und Illustratoren, Agenten und Journalisten, Informationsvermittler und Leser. Gleichzeitig bietet die Frankfurter Buchmesse den weltweit größten Marktplatz für den Handel mit Rechten und Lizenzen in der Verlagsindustrie. Gerade die Registrierung als Fachbesucher durchlaufen. Mal schauen, was dabei rumkommt. Der Besuch der Frankfurter Buchmesse ist eigentlich eine Pflichtveranstaltung für jeden Entwickler... Ich hoffe, dass ich dabei auch ein paar Personen von O'Reilly Deutschland sowie Microsoft Press offline zu einem Gespräch antreffe. Nunja, die nächsten Tage mal schauen. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'frankfurter-buchmesse.html'; this.page.identifier = '9271ed1c97_idfrankfurter-buchmesse'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "freizeit.html": {
    "href": "freizeit.html",
    "title": "Freizeit? - Get Blogged by JoKi",
    "summary": "Freizeit? Copy Markdown View Markdown GitHub Markdown Source Freizeit? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'freizeit'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'freizeit'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Freizeit?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-12-15 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); All work and no fun make Jack a dull boy Mann, mann, mann... So langsam komm' ich mir wie Jack vor. Aktuell gibt's soviel Programmierarbeiten und privaten Kram zu erledigen, dass ich mich manchmal frage, welchen Wochentag wir eigentlich gerade haben. Ah, Samstag, joah, dann sollte ich vllt. mal nach den nächsten 50 Zeilen Code irgendwie einkaufen oder so gehen... Hm, im Moment bleiben die Vorbereitungen für den Abschlußstammtisch 2005 ein wenig liegen, aber... das ist zu erledigen und wird erledigt. Punkt. Ansonsten gehen die Planungen bereits wieder bis ins Frühjahr 2006. Ich bin mal gespannt, noch alles bis an Ostern anfallen wird. Es wird spannend werden bzw. bleiben. \uD83D\uDE09 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'freizeit.html'; this.page.identifier = '9271ed1c97_idfreizeit'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "fuchs-nicht-vom-aussterben-bedroht.html": {
    "href": "fuchs-nicht-vom-aussterben-bedroht.html",
    "title": "Fuchs NICHT vom Aussterben bedroht - Get Blogged by JoKi",
    "summary": "Fuchs NICHT vom Aussterben bedroht Copy Markdown View Markdown GitHub Markdown Source Fuchs NICHT vom Aussterben bedroht Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fuchs-nicht-vom-aussterben-bedroht'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'fuchs-nicht-vom-aussterben-bedroht'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Fuchs NICHT vom Aussterben bedroht') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-12-17 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Die öffentlichen Artikel zu Visual FoxPro mehren sich doch in der letzten Zeit. Sei es in Verbindung mit den aktuellen Entwicklungen zum Produkt selbst - Service Pack 1 ist veröffentlicht - oder zu den kommenden Entwürfen von Microsoft in Bezug auf die Weiterentwicklungen im .NET Framework. Die Tage habe ich mir den folgenden Artikel des Redmondmag.com: FoxPro Not an Endangered Species https://www.redmondmag.com/columns/article.asp?EditorialsID=1166 durchgelesen. Sehr interessant und potentiell aufschlussreich. Hier mal ein paar Aussagen, die mir persönlich als hochgradig wichtig erscheinen: \"That's mainly because the Microsoft dev team has figured out it has a lot to gain from FoxPro.\" \"Microsoft officials admitted earlier this year that the developer division is borrowing from FoxPro for Visual Basic 9.0 and LINQ, the Language Integrated Query add-ons that the company is developing for the next iterations of Visual Basic and Visual C#.\" \"Features in Sedna will target Visual FoxPro interoperability with application components created by using Visual Studio 2005, the .NET Framework 2.0, and SQL Server 2005.\" Natürlich werden nun wieder einige Unkenrufe durch die Luft hallen: \"FoxPro ist am Ende\" - Leute blickt den Tatsachen ins Auge: VFP is stronger than ever. Insbesondere die Tatsache, dass LINQ überhaupt entstanden ist, sehe ich ebenfalls als schlagendes Argument dafür, dass VFP gerade bei der Planung und Realisierung von .NET 3.0 noch viele weitere Steilvorlagen bieten wird. Hat schon mal jemand die neuen MaskInput-Controls in Visual Studio genutzt? Cooles Feature provided by Visual FoxPro... Nunja, aus Redmond werden auch 2006 noch sehr viele Gerüchte, Marketingaktionen und Dementi kommen. Ich bin auf alle Fälle gespannt... Achja, im Gegensatz zu anderen Tierarten wink-wink zählt der Fuchs nicht zu den vom Aussterben bedrohten. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'fuchs-nicht-vom-aussterben-bedroht.html'; this.page.identifier = '9271ed1c97_idfuchs-nicht-vom-aussterben-bedroht'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "funstuff-mit-pferden.html": {
    "href": "funstuff-mit-pferden.html",
    "title": "Funstuff mit Pferden - Get Blogged by JoKi",
    "summary": "Funstuff mit Pferden Copy Markdown View Markdown GitHub Markdown Source Funstuff mit Pferden Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'funstuff-mit-pferden'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'funstuff-mit-pferden'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Funstuff mit Pferden') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-06-12 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Boah, wie cool!! https://svt.se/hogafflahage/hogafflaHage_site/Kor/hestekor.swf Die Pferde sind klickbar - achja, Sound anmachen if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'funstuff-mit-pferden.html'; this.page.identifier = '9271ed1c97_idfunstuff-mit-pferden'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "future-of-active-foxpro-pages-secured.html": {
    "href": "future-of-active-foxpro-pages-secured.html",
    "title": "Future of Active FoxPro Pages - secured - Get Blogged by JoKi",
    "summary": "Future of Active FoxPro Pages - secured Copy Markdown View Markdown GitHub Markdown Source Future of Active FoxPro Pages - secured Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'future-of-active-foxpro-pages-secured'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'future-of-active-foxpro-pages-secured'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Future of Active FoxPro Pages - secured') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-09-29 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Finally some official news about Active FoxPro Pages, aka AFP. The German company BvL Bürosysteme Vertriebs GmbH bought all rights of Active FoxPro Pages from the insolvency stock. Being a former customer and intensive user of AFP since version 2.0 BvL has own interest in the continuation of AFP on current and future web servers. Together with their partners Christof Wollenhaupt (Foxpert Software Development & Consulting) and Jochen Kirstätter (IOS Indian Ocean Software Ltd) BvL will continue with development, support and marketing of AFP in the upcoming weeks. There will be an updated version of AFP, the relaunch of the website, re-enabling of activation server, re-establishment of support channel, and much more... Personally, I am relieved that this superb product made its way out of the dust of the past years. And of course, to be involved (again) in the development and support of Active FoxPro Pages gives me a big smile. Rest assured that there will be more articles on AFP soon! Here is the original announcement of 27th September 2010 from the online forum of German FoxPro Usergroup (dFPUG) - section Active FoxPro Pages: *Liebe AFP Anwender, liebe FoxPro Gemeinde, nach den Insolvenzen der ProLib Software GmbH und der ProLib Tools GmbH gab es einige Verunsicherung über die Zukunft der Active FoxPro Pages.* * Wir können euch nun mitteilen, dass eine für alle Beteiligten positive Lösung gefunden wurde. Wir, die BvL Bürosysteme Vertriebs GmbH aus Berlin, haben sämtliche Rechte an der AFP aus der Insolvenzmasse vom Insolvenzverwalter abgekauft. Bereits 1987 wurde die BvL Bürosysteme Vertriebs GmbH gegründet und hat sich seit dem erfolgreich im Markt bewährt. Wir gehören auch schon seit Foxpro2.0 zur Foxpro-Gemeinde und auch mit der AFP2.0 haben wir unseren Einstieg in die AFP-Gemeinde vollzogen. Wir wollen die AFP nicht in irgendeine Schublade packen, sondern unser Ziel ist es, die AFP weiterzuentwickeln, speziell auch auf die kommenden Serverversionen. Unter der Homepage www.active-foxpro-pages.de wird es demnächst einen neuen Auftritt geben. An den Preisen soll sich nichts groß verändern, das Handbuch soll anständig aufgelegt werden und selbstverständlich soll der Support und die Weiterentwicklung eine große Aufmerksamkeit bekommen. Mit Christof Wollenhaupt und Jochen Kirstätter haben wir zwei Partner an Bord, die sich um den Support und die Weiterentwicklung kümmern werden. Christof Wollenhaupt wird maßgeblich und federführend an der Weiterentwicklung beteiligt sein. Über Christof Wollenhaupt können auch ab sofort Lizenzen gekauft werden, Christof Wollenhaupt ist für den Online-Vertrieb zuständig, der gerade aufgebaut wird. Sollte ein AFP Server aktiviert werden müssen, können sich alle bisherigen Lizenzinhaber auch direkt an Christof Wollenhaupt wenden. In den nächsten Wochen werden wir die AFP wieder auf Touren bringen.* * Eine aktuelle Version, eine neue Webseite, der Aktivierungsserver, ein Überblick über das leicht geänderte Lizensierungsmodell, und vieles mehr ist gerade in Arbeit. Die Zukunft und die Weiterentwicklung der AFP sind jetzt gesichert!* * Mit freundlichen Grüßen* Ralph-Norman von Loesch Source: https://forum.dfpug.de/bodyframe.afp?msgid=728069 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'future-of-active-foxpro-pages-secured.html'; this.page.identifier = '9271ed1c97_idfuture-of-active-foxpro-pages-secured'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "gads-2020-spotlight.html": {
    "href": "gads-2020-spotlight.html",
    "title": "GADS 2020 - Spotlight - Get Blogged by JoKi",
    "summary": "GADS 2020 - Spotlight Copy Markdown View Markdown GitHub Markdown Source GADS 2020 - Spotlight Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gads-2020-spotlight'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gads-2020-spotlight'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'GADS 2020 - Spotlight') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-10-06 - Filed under AndelaCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Following my recent announcement of being a GDE for Cloud Emem Jonah (@ememjonah2) got in touch with a few questions about what motivates me to be a software developer. Emem is a Program Assistant (PA) in the current Google Africa Developer Scholarship 2020 program and among other tasks runs the weekly Happy Hour activities on our Slack workspace. Following are her questions and my remarks on each. This spotlight was published during the happy hour session on Friday, 2nd of October 2020. Kindly use the comment section below the article if you would like to know more about anything mentioned. Tell us about Yourself \"The only frontiers are in your mind\" Hi, my name is Jochen Kirstätter, also known as \"JoKi\". I'm a software craftsman, blogger, community founder, and speaker. Although I have been awarded as Microsoft Most Valuable Professional (MVP) for Developer Technologies and as Google Developers Expert (GDE) for Cloud I neither work for Microsoft nor for Google. These awards are a recognition of my community contributions, mu passion to share knowledge, and my activities to engage with their respective services and products over years. What inspires You? Nature and infinite curiousity. Being able to explore and discover any kind of things as part of a daily learning experience is a fantastic motivator. Whether it is about living creatures in nature, complex relationships in science or interesting research and developments in technology, it's all about learning. Especially as a professional craftsman in software development. How does your typical day look like? After waking up the kids in the morning to ready them for school I take roughly an hour to go through the latest emails, tweets, and Slack posts of GADS 2020 that happened during the night. Concluding I might read a few technical articles and have a look at current learning video or course I'm attending. Then it's either driving to the office or staying for Home Office to work on customer assignments and projects. Usually I would skip lunch to keep an eye on my weight and to avoid a heavy stomach during the afternoon. The afternoons are also full on software development. After returning home I would usually take a break to spend some time with the family - like 3-4 hours between dinner time and after all kids went off to bed. Depending on my fatigue level I would use the late night hours to catch up on community activities like GADS, MSCC and GDG. Of course, there are days off and days that I spend on writing articles for my blog or reserve for other activities. What do you do when you are not coding? Reading. I'm a big fan of the Amazon Kindle and I have an Unlimited subscription which allows me to read a lot of books and magazines for a little fee per month. Most of the time I'm into sci-fi and fantasy stories but also biographies. Recently I read about JRR Tolkien and at the moment I'm into the John Carter series of seven books. Next up, I'm going to read about Nikolas Tesla and his achievements during the last century. If time and weather allows I might consider some scuba diving here and there. Looking forward to have my two elder kids joining me soon under water. What do you do when you get stuck? I'm practicing my Google-Fu and search the internet for similar reports on the problem I'm dealing with. Clearly, there are low chances that I possible could be the first and only person being confronted with a problem. In the rare case that nothing comes up I reach out to my network of community friends and experts and ask them. And if anything else doesn't offer a solution I reach out to the official support of the supplier. FYI, most of the time it usually turns out that I didn't pay attention to the documentation or I might have missed to read something. Reading is super important in software development. What advice would you give someone who is just starting out? I would like to speak about two advices to ease your life. First, learn to read information correctly. Don't just skim certain articles and tutorials but take your time to actively read the information. I've observed it many times in previous ALC programs and during GADS 2020 that numerous learners got stuck because of not reading what's either in the instructions or in the error response of the system. And second, chunk up your day into small units of let's say approximately 30 mins. Each unit is then reserved for one(!) single task. Disable all kind of notifications and distractions on your computer and mobile while you're working on an assignment, doing a code lab, or reading a tutorial. Personally, I check my emails only four times a day to respond. Also, schedule learning units and block your time just for that. Thanks Emem for those interesting questions and I hope that some of my thoughts and experience might give our learners here in the program some inspiration. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'gads-2020-spotlight.html'; this.page.identifier = '9271ed1c97_idgads-2020-spotlight'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "game-another-day.html": {
    "href": "game-another-day.html",
    "title": "Game another day - Get Blogged by JoKi",
    "summary": "Game another day Copy Markdown View Markdown GitHub Markdown Source Game another day Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-another-day'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-another-day'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Game another day') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-26 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nach einem anstrengenden Tag der Systemeinrichtung eines Rechners, der Programmierung und einigen Modifikationen am AfpWiki war es wieder einmal soweit... Gaming bis der Notarzt kommt! Mein PC ist mir ehrlich gesagt für sowas wesentlich zu schade, daher vertrete ich schon seit Jahren den Ansatz, dass dafür eine Konsole her muss. Zudem verstehe ich auch selten, wie man mehr Geld für eine Grafikkarte wie für den gesamten PC ausgeben kann. Aber okay, wir wollen ja keine Gamer auslachen. Apropos Konsole, da gibt's für mich nur eine: Nintendo GameCube - yeah, Flame On! Wieso? Nun, Nintendo ist der Innovator in diesem Segment, und das seit Jahrzehnten. In der Vergangenheit konnte wenigstens noch Sega einiges entgegensetzen aber heutzutage ist von der Konkurrenz nur wenig Innovation zu erwarten. Wobei ich gestehen muss, dass das Sony EyeToy die Ausnahme von der Regel darstellt... Aber ansonsten besitzt der GC meines Erachtens den höchsten Fun-Faktor. Und irgendwelche strategische Ballereien zähle ich nicht zu Fun-Faktor sondern zu Stress. Letztendlich ist es doch wiederum Geschmackssache - denn wir wissen ja alle, dass Seife wunderbar schmeckt und zarte Lippen macht. Außerdem wird selten alleine gezockt. So auch diesen Abend / Morgen - mit von der Party die Herren G. und T. - Well, was zockt man denn nun zu dritt - klare Antwort: Mario Party! Zwar die etwas ältere Version 4, aber dennoch der Spassfaktor überhaupt. Vor allem wenn man einen 'Frischling' dabei hat. Mit kurzem Break für das Abendessen - nein, keine Pizza oder sonstiges FastFood, sondern leckere Lachsfilet in Sahne mit Reis - gingen die Stunden vorüber. Naja, als Ausklang noch ein paar Zeilen für euch. Habt euch wohl und esst brav eure Teller leer. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'game-another-day.html'; this.page.identifier = '9271ed1c97_idgame-another-day'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "game-review-god-of-light.html": {
    "href": "game-review-god-of-light.html",
    "title": "Game Review: God of Light - Get Blogged by JoKi",
    "summary": "Game Review: God of Light Copy Markdown View Markdown GitHub Markdown Source Game Review: God of Light Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-review-god-of-light'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-review-god-of-light'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Game Review: God of Light') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-04-08 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Luckily I came across this title at a very early stage. If I remember correctly, I took notice of God of Light on Twitter right on the weekend it has been published on the Play Store. \"Sit back and become immersed into the world of God of Light, the game that rethinks the physics puzzle genre with its unique environment exploration gameplay, amazing graphics and exclusive soundtrack created by electronic music icon UNKLE. Join cute game mascot, Shiny, on his way to saving the universe from the impending darkness. Play through a variety of exciting game worlds and dozens of levels with mind-blowing puzzles. Your goal is to explore game levels, seek for game objects that reflect, split, combine, paint, bend and teleport rays of light energy to activate the Sources of Life and bring light back to the universe.\" Mastering the various reflection items in God of Light is very easy to learn and new elements are introduced during the game. Amazing puzzle game Here's the initial review I posted on the Play Store: \"Great change in puzzles Fantastic and refreshing concept of puzzle solving. The effects and the music match very well, putting the player in the right mood to game. Get enlightened and grow your skills until you are a true God of Light.\" And it remains true, even after completing the first realm completely. Similar to Quell it took me only a couple of hours during the evening to complete all levels in the available three realms, unfortunately. God of Light currently consists of 75 levels, well it's 25 in each realm to be precise, and the challenges are increasing. Compared to the iOS version from the AppStore, God of Light is available for free on Android - at least the first realm (25 levels). Unlocking the other two remaining realms is done through an in-app purchase. The visual appearance, the sound effects and the background music provided by UNKLE makes God of Light a superb package for any puzzle gamer. Whether it is simply reflecting light over multiple mirrors, or later on bending the rays of light with black holes, or using prisms to either split, enforce, or colourise your beam, God of Light is great fun and offers a good amount of joy. Check out the following screenshots for some impressions. God of Light: Astonishing graphics and visual appeal throughout the game God of Light - Introduction to the game during the first levels. New light items are introduced at each stage during the game play God of Light: Increasing complexity and puzzle fun Hopefully, Playmous is going to provide more astonishing looking realms and interesting gimmicks in future versions. Play Store: God of Light Also, check out the latest game updates on the official web site of Playmous if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'game-review-god-of-light.html'; this.page.identifier = '9271ed1c97_idgame-review-god-of-light'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "game-review-monument-valley.html": {
    "href": "game-review-monument-valley.html",
    "title": "Game Review: Monument Valley - Get Blogged by JoKi",
    "summary": "Game Review: Monument Valley Copy Markdown View Markdown GitHub Markdown Source Game Review: Monument Valley Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-review-monument-valley'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-review-monument-valley'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Game Review: Monument Valley') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-08-16 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Once again, it was a tweet that caught my attention... and the official description on the Play Store sounds good, too. \"In Monument Valley you will manipulate impossible architecture and guide a silent princess through a stunningly beautiful world. Monument Valley is a surreal exploration through fantastical architecture and impossible geometry. Guide the silent princess Ida through mysterious monuments, uncovering hidden paths, unfolding optical illusions and outsmarting the enigmatic Crow People.\" So, let's check it out. What an interesting puzzle game Once again, I left some review on the Play Store: \"Beautiful but short distraction Woohoo, what a great story behind the game. Using optical illusions and impossible geometries in this fantastic adventure of the silent princess just puts all the pieces perfectly together. Walking the amazing paths in the various levels and solving the riddles gives some decent hours of distraction but in the end you might have the urge to do more...\" I can't remember exactly when and who tweeted about the game but honestly it caught my attention based on the simplicity of the design and the aspect that it seems to be an isometric design. The game relies heavily on optical illusions in order to guide to the silent princess Ida through her illusory adventure of impossible architecture and forgiveness. The game is set like a clockwork and you are turning, flipping and switching elements on the paths between the doors. Unfortunately, there aren't many levels and the game play lasted only some hours. Maybe there are more astonishing looking realms and interesting gimmicks in future versions. Play Store: Monument Valley Also, check out the latest game updates on the official web site of ustwo BTW, the game is also available on the Apple App Store and on Amazon Store for the Kindle Fire. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'game-review-monument-valley.html'; this.page.identifier = '9271ed1c97_idgame-review-monument-valley'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "game-review-quell.html": {
    "href": "game-review-quell.html",
    "title": "Game Review: Quell - Get Blogged by JoKi",
    "summary": "Game Review: Quell Copy Markdown View Markdown GitHub Markdown Source Game Review: Quell Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-review-quell'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'game-review-quell'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Game Review: Quell') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-01-10 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Only by chance reading a posting from one of my FB contacts I got a hint about Quell. \"Quell is an enchanting game of logic which has taken the puzzle world by storm. Don’t take our word for it! Read our user reviews (thanks guys!), and see for yourself!\" And I'm very glad that I went for it. Great puzzle game Quell is a puzzle game in its own league. I mean seriously... it keeps you captivated. Quell is available on the Google Play Store in two editions - one free, ad-supported version and a paid, ad-free one -, namely Quell and Quell+. Don't get confused, it's still the same game after all. Quell's visual appeal lies in the details. Your task is to push a water drop (the quell) in order to collect coins in various mazes within a given number of moves. The aim in each level is to solve the puzzle in the perfect number of moves necessary. Starting in the past you are faced with more 'simply' challenges and the guidance steps are clearly understandable. From stage to stage, from level to level difficulty increases slowly but steadily. New obstacles and features are introduced as well. I took me a couple of evening hours to complete all levels but it was worth the time. Following some impressions taken from the actual game and the links to the Play Store. Quell: Level overview in a stage Quell: Push the water drop to collect all coins in a level Play Store: Quell (free edition) & Quell+ Don't miss the other games of FallenTreeGames: Quell Reflect and Quell Memento. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'game-review-quell.html'; this.page.identifier = '9271ed1c97_idgame-review-quell'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "gastbesuch-bei-vfp-stammtisch-stuttgart.html": {
    "href": "gastbesuch-bei-vfp-stammtisch-stuttgart.html",
    "title": "Gastbesuch bei VFP Stammtisch Stuttgart - Get Blogged by JoKi",
    "summary": "Gastbesuch bei VFP Stammtisch Stuttgart Copy Markdown View Markdown GitHub Markdown Source Gastbesuch bei VFP Stammtisch Stuttgart Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gastbesuch-bei-vfp-stammtisch-stuttgart'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gastbesuch-bei-vfp-stammtisch-stuttgart'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Gastbesuch bei VFP Stammtisch Stuttgart') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-10-09 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Auf Einladung, oder sollte ich sagen besser 'Revanche' sagen?, von Armin Neudert (dFPUG Regionalleiter Stuttgart) bin ich am FR Abend in Herrenberg bei Stuttgart anzutreffen. Dankeswerterweise darf ich den Stuttgarter Stammtisch für eine Generalprobe einer AFP-Session für die Devcon 2004 'mißbrauchen'. Ich bin mal gespannt, wie fertig ich da raus komme... Wenn überhaupt lebend \uD83D\uDE01 Aber es freut mich auch, wieder an einem Treffen mit Entwicklerkollegen aus weiteren Regionen teilzunehmen. Die meiste Zeit kommunizieren wir entweder über das dFPUG Forum, die MS Newsgruppen oder direkt per Mail. Da empfinde ich diese Offline-Treffen als sehr viel persönlicher und vorallem wesentlich spassiger. Okay, ich bin selbst mal gespannt, was mich erwartet - vorallem ob ich es überhaupt finde... Sozusagen eine doppelte Premiere - erstes Mal auf dem Stammtisch in Stuttgart und mit einer druckfrischen Session im Gepäck. Achja, da ich mit dem Auto unterwegs sein werde... Es sind noch potentiell 2 Plätze frei. Falls jmd. mitfahren möchte? Bitte kurz melden, dann machen wir was aus. Bis denne, JoKi Image courtesy of Prerna Bhardwaj if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'gastbesuch-bei-vfp-stammtisch-stuttgart.html'; this.page.identifier = '9271ed1c97_idgastbesuch-bei-vfp-stammtisch-stuttgart'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "gde-ssa-summit-2022.html": {
    "href": "gde-ssa-summit-2022.html",
    "title": "GDE SSA Summit 2022 - Get Blogged by JoKi",
    "summary": "GDE SSA Summit 2022 Copy Markdown View Markdown GitHub Markdown Source GDE SSA Summit 2022 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gde-ssa-summit-2022'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gde-ssa-summit-2022'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'GDE SSA Summit 2022') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2022-09-27 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Did you ever hear about the Google Developers Expert (GDE) Summit in the Sub-Saharan Africa (SSA) region? No? Well, then let me tell you it was the same experience for me. This year, read: 2022, was the first-time ever that the community leads at Google Lagos, Nigeria organised the regional GDE SSA Summit and invited approximately 30 experts from all over the African continent to Kigali, Rwanda. Reading about the GDE summit and getting the chance to attend this in-person event stirred up quite a bit of excitement in me. It's not like I haven't been travelling lately. It's more about the outlook to meet some old friends and to probably make new friends. Preparations Frankly, not much to report about any preparations on my side. The GDE leads chose Rwanda due to its travel-friendly VISA regulations for citizens from other African countries. For me, as German national, Rwanda stipulates a VISA on arrival. No surprises here, and it's very similar to the procedures I had while travelling to the GDG SSA Summit in Nairobi, Kenya back in 2018. However I was struggling a bit with my Lenovo Chromebook and was wondering whether I should drop it off for servicing prior to the trip or afterwards only. The keyboard was acting up and even a so-called power-wash to wipe the system completely as recommended by several sources online didn't bring me any improvements. Luckily I decided to keep it, paired it with an Logitech K380 keyboard and a Logitech Ergo M575 trackball via Bluetooth and I was good to go. Oh yeah, of course I packed a few clothes as well, but not too many. Read: no checked-in luggage, cabin luggage only (highly recommended for short trips). Technical and non-technical equipment Not sure whether this might be of interest but I'd like to jot down the stuff I brought for myself. Maybe it's an inspiration for you. Lenovo Chromebook (USB-C charging) Lenovo ThinkPad 65W charger (USB-C) Google Pixel 6a (USB-C charging) Google Buds Pro (ANC and USB-C charging) 10,000 mAh battery power pack (USB-C charging) Logitech K380 keyboard (battery-powered; up to three BT devices) Logitech Ergo M575 trackball (battery-powered; Logitech Unify and BT) Those are mainly the technical gimmicks that went into my cabin luggage. The focus is clearly on everything using a single connector, USB-C. With that single power-unit I'm able to charge all devices. Now onto some essential utilities I always bring along while travelling. High-performance outdoor towel (easy to clean; fast drying) Spare face masks First-aid travel pack with disinfectant and band-aids Mosquito repellent Vitamin C (powder or dragees) And that's about it. Again, traveling light is key on such kind of short trips. If ever there would be any need I can easily go to one of the local grocery shops, supermarkets, or pharmacies to purchase whatever is missing. Travelling (to or) within Africa I know, I know, Mauritius is really located at the most Eastern edge of the continent and even some people might say that the island isn't Africa anymore but Asia. Yes, it is Indian Ocean territories you might say and I actually like the circumstances that Mauritius belongs to three realms on the globe. Which sometimes doesn't make it easier for us islanders to travel west-wards. There are several routes available; obviously with varying travel times as well as cost involved for the air tickets. Let me give you some ideas. Mauritius - Johannesburg - Kigali Mauritius - Nairobi - Kigali Mauritius - Dubai - Nairobi - Kigali Mauritius - Dubai - Entebbe - Kigali Mauritius - Johannesburg - Harare - Kigali etc. Given those few examples you might imagine the travel times and cost involved. Total flight times ranged between 15 hours and over 40 hours, whereas prices for air tickets somewhere between USD $700 at the lower end and close to or even beyond USD $4,000 at the upper end. Travelling in economy class... not that you might expect business class or anything more comfortable. A number of route / airline combinations surprisingly turned out way more expensive than expected and others would cost less but would also take a larger chunk of time out of your personal schedule. Well, after a few hours exploring available options forth and back, and email exchange with the travel agency I finally decided to stick with the known. I traded a couple of additional hours for the comfort and services of Emirates while keeping the expenses for the air tickets within an acceptable range. Finally, I chose the two-stop, long-way round trip via Dubai and Nairobi. Thanks to past travelling experience, ie. SSA Community Summit 2018, I know both airports and the facilities they offer. It had always been manageable to spent a couple of hours there. Also considering easy access to their corresponding business lounges for travellers. Can't imagine the adventure it might be travelling to a future GDE SSA Summit on the western coast of Africa, let's say Guinea, Mauritania or Ivory Coast. Whew, surely that would be exciting but also very exhausting. Hello Kigali What a beautiful city you are! Honestly, I have been slightly gobsmacked after exiting and leaving the airport even at the night. Everywhere you could look it was both very clean and very green. No graffiti on the wall, no dirt or rubbish on the streets or in the curbs, lots of plants, trees, and flowers anywhere. Proper lighting and fantastic state of roads. Speaking of roads, the Kigali is in close vicinity of the city and therefore speed limits of the city apply - a general limit of 60 km/h. Which is great and safe at the same time. Plan your car ride well ahead of schedule and don't rush around the suburbs. Quite impressive I have to say, and surely European cities can learn a bit or two from Kigali. While waiting for the transfer to the hotel I was exchanging a little bit with our GDE leads Eunice and John who told me that other GDEs would arrive soon from Johannesburg due to delayed flights. Alright, let's see who's it going to be. First came Android GDE Ahmed Tikiwa and shortly after we were joined by Android GDE Maia Grotepass. Finally, Eunice came together with Josue Mutabazi, who runs the GDG Kigali to give us a heart-warming welcome. Great to meet such lovely people at that time and talking about the various travel experiences. Interestingly both Ahmed and Maia came from Cape Town however on different routes with different obstacles and observations. Lastly, we got an additional car to cater for our transfer to the hotel and off we went. On the way I also noticed that the local offices of one of our partners of the annual Developers Conference - Liquid Technologies - are close to the hotel. Arriving at the hotel late in the evening took a toll on my fitness. After I had a quick shower and video chat with my wife I headed straight into the bed and caught some Zzzz's. Thanks to the difference in time zones - GMT+2 vs GMT+4 - I won two hours extra sleep during the night, I mean psychologically seen. Therefore I was well recovered and re-energized in the morning. Rise and shine! First day: Get to know each other (more) During breakfast I joined a few early folks and together we were joined by more GDEs and our community leads. Whew, it has been four years since I last time spoke to John Kimani as well as Auwal Samu in-person. It was also great to meet again with Robert John after his speaker appearance at DevFest Mauritius back in 2019. During DevFest Nairobi 2018 I attended an interesting session by Kenneth Kinyanjui on Progressive Web Apps (PWA) who joined us a little later morning. Plus, there were lots of other experts like Hannah Olukoye and Maina Wycliffe joining in. Ohhh, and I was delighted to meet Ezekias Bozoke who participated in this year's Road to GDE mentorship program. Why you might wonder? Initially I was chosen to mentor Ezekias during that journey to become a GDE however due to my limitations in speaking technical French I requested that he'd be reassigned to another French speaking GDE, Guillame Blaquiere. I'm really happy that Ezekias became a GDE. Now, well fed and full of energy it was time to explore the hotel a little bit before the meetings of the GDE SSA Summit would start. Heading back to my room I came across Alex Koller in the hallway. He seemed a bit surprised that I recognised him or maybe it was just too early. Who knows. OK, back to hotel facilities, so the gym as well as the heated pool are on first floor and just around the corner of the meeting room of the summit. Hmm, maybe we should spent the afternoon talks in and around the pool. Great stuff coming for GDEs and for Africa The main topic during the summit was Next Billion Users. All about the latest news and trends about Google's new products and features for the next billion users. And its $1 billion commitment to Africa. And there will GDE membership certificates coming. Hoorah! Due to the constraints and difficulties caused by the pandemic there had been honorable mentions of a couple of GDEs who remained active during those tempting times. Can you imagine my surprise seeing myself on that slide? Apart from that COVID stuff I had struggles of my own during 2021 and I'm still impacted by aftermath. Big kudos and shout out to all other GDEs. You are amazing and we all do an amazing voluntary work in our region. CcHub Design Lab Kigali To avoid hanging around the hotel all day long a local GDE organised a kind of Meet & Greet mentoring workshop kind of style date for us. So we went on a short trip from the hotel to the Co-creation Hub (CcHub) Design Lab in Kigali. Apart from some interactive session among ourselves we were asked to offer expert advice to a group of start-ups which are part of the CcHub development program. Dragon's Den Light We were divided into several groups and then it was time for \"Dragon's Den\". Listening to their start-up ideas and their \"sales pitches\" was pretty good fun, and we enjoyed ourselves with a number of questions towards the participants.Especially regarding their choices of technological stack, cloud computing provider and legal constraints they might be facing. Second day: More community and even more fun Like the previous morning I woke up full of energy and ready for another day of community and fun. But first duty called. Together with a few GDEs for Google Cloud I had a joined meeting with Googlers in Singapore to join our experience and views on Google Cloud Platform for Africa. The major question was 'How can GCP be ready for the next billion users?' and the specific requirements of African people and governments. In general, data access on mobile is still scarce and expensive across the continent, and yes, back in Mauritius we can be really happy with what we already have compared to other African countries and regions. Both in regards to bandwidth capacity as well as affordability of mobile data plans. Right before lunch time we had concluded the official parts of the SSA GDE Community Summit agenda and it was time for more entertainment that would require our full attention, might and skills to complete the tasks ahead of us. To escape or not to escape, that's the question Following a brief bus ride through the beautiful city of Kigali we stopped on the parking area of a football pitch or kind of community playground. Turns out the nearby located facility has been converted into an Escape Game. We have been split into two groups - each tasked with different challenges - and the competitive game play was on. Who would be first to escape? Honestly, our group had additional challenges to master but at the end we finally managed to escape alive. After all that excitement and challenges we escaped to a cool location for lunch time. The place has an open space for large gatherings with a authentic rustic kitchen area in one fo the cursors sporting a furnace made out of bricks. Very chilled atmosphere and great ambiente to hang out and just network. A picture says more than a thousand words Next, we drove across half the city for an artistic session. \"Paint & Drink\" was the motto of the afternoon reaching one of the rooftop bars in downtown Kigali. Each of us was greeted with a cocktailof choice and... an empty canvas. This was a completely new experience for me. I have never done anything like that before... However it was good fun and entertainment. Some paintings are really impressive pieces of art. Surely not mine. \uD83D\uDE09 Celebrating dinner together To wrap up the day we were hanging out for dinner at another cool location called The Hut. That dinner was purely networking and having great conversations with other GDEs. With further progress I was enjoying myself speaking with a group of fancophone GDEs, among them Ezekias, and we had good laughters all around. It was great to learn more about their countries and the activities of their communities there. DevFest Kigali Hah! You might expect a DevFest to start in the early morning hours, like probably as from nine o'clock on-wards or so. Not in Kigali my friend. Or at least not on any last Saturday of a month. Turned out that on such a day people are not allowed to be on the roads before noon because of city-wide (national) sanitation. Meaning, the streets are cleaned and maintained by public service. Therefore vehicles, motorbikes as well as pedestrians have to stay off them. Woah... never heard of that. Impressive: Kigali - Cleanest city Note: It's called Umuganda Well, no problem for me. I slept long that morning, took a very late breakfast and then decided to hop into the heated pool for another hour and chill. Eventually other GDEs took notice and I got company by Ahmed and Robert - both preferred to stay on the pool loungers though. Robert gave me a couple of Arduino devices that I should handover to the Makers community in Mauritius. Ahmed on the other hand shared some funny anecdotes about the power load-shedding in South Africa and a couple of personal experiences. He was also genuinely interested in the situation of IT in Mauritius. This year's edition of DevFest Kigali was organised at a newly co-working place called Norrsken. Actually, it's a Danish company that created a facility for young start-ups in Rwanda to launch their business ideas. As Norrsken is located in walking distance to our hotel a few of us GDEs met in the lobby and went together there. The organising team of GDG Kigali decided to go for a mixed setup. Two registration desks - one for attendees, one for speakers and helpers - were located outdoors right after the entrance. Easy to spot and lots of space to gather around the tables to receive a precious badge. Directly next to those desks was a large pavilion with an approximate seating capacity of 150 to 180 seats. Visually an amazing place to welcome attendees at DevFest Kigali and to conduct the opening keynotes held by John on anything Google as well as Chuka Ofili about fruitful advice on personal and professional career development. Afterwards, the schedule split into multiple tracks and the audience was guided to three additional break-out areas based on their interest and choice of presentation. Although I went to explore the co-working space between my arrival at Norrsken and the official start of DevFest I finally decided to stay in the pavillon and attended the presentation on Android development using Flutter by GDE Hannah Olukoye and the art of story-telling by fellow GDE Maia Grotepass. The talks were great and really helpful. The Q&A periods however added another level of fun to it because of the high number of Google Developer Experts present. Literally no question remained unanswered. When one of the speakers couldn't give advice straight away one or multiple GDEs chipped in and shared from their experience. What a spirit of community. And opportunity for DevFest attendees. Unfortunately, I couldn't stay for the whole day of DevFest Kigali but had to head back to the hotel collecting my stuff and then enjoyed a smooth ride to the airport together with Wayne Gakuo and Dhanija. My long journey back was coming up next. Thanks and kudos A big shout-out to our regional leads of the GDE and GDG programs (in no particular order) - John, Eunice, Auwal, Dhanija, Ada, and Sodiq - this first-ever GDE SSA Summit went down like a breeze. The mixture of informative presentations, fun sessions, community activities, and evening program was well-balanced, well organised and executed. The choice of inviting us to Kigali was fantastic and a hassle-free experience of travelling (at least for me and I had fun despite the long hours). All the best and lots of success to Femi and the lovely people at CcHub Kigali in their quest to discover fresh entrepreneurs and nourish their creative talents for the benefit of Rwanda and perhaps whole Africa. Would love to see success stories coming from there. And a big Thank You to GDG Kigali for organising their annual DevFest event at the same time. Giving some of us GDEs not only a chance to speak in front of audience but also to network with the attendees. I really enjoyed meeting some folks from the SSA Community Summit 2018 and a few Twitter people. Thanks to anyone involved which I missed mentioning. It's not intentional and you're appreciated. Thank You. More impressions Words can hardly describe the atmosphere during those events and activities described above. Following are a couple more impressions from my three days spent in Kigali. See for yourself and kindly consider to visit Kigali in the future. On my side, I already suggested to my wife that we should come back/again to Rwanda. Even for the children it seems an interesting country to explore. In case that you visited Rwanda and you'd like to share some tips. Please leave your remarks and suggestions in the comment section below. That's really appreciated from me. Note: Some of the links above are affiliate links which don't hurt anyone's wallet but provide a little bonus for my personal interest in books and other gadgets. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'gde-ssa-summit-2022.html'; this.page.identifier = '9271ed1c97_idgde-ssa-summit-2022'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "gdg-cloud-munich.html": {
    "href": "gdg-cloud-munich.html",
    "title": "Speaking at GDG Cloud Munich: State of GCP (.NET Edition) - Get Blogged by JoKi",
    "summary": "Speaking at GDG Cloud Munich: State of GCP (.NET Edition) Copy Markdown View Markdown GitHub Markdown Source Speaking at GDG Cloud Munich: State of GCP (.NET Edition) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gdg-cloud-munich'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gdg-cloud-munich'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Speaking at GDG Cloud Munich: State of GCP (.NET Edition)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-11-18 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ahead of the Google I/O Connect event in Amsterdam there had been exchange with the organisers of GDG Cloud Munich. Thankfully, they accepted a proposal to speak. Why Munich, you ask. It's quite far from Amsterdam. Connecting with GDG Cloud Munich Given the circumstances that I had to do some of my annual health check-ups and examinations in Germany I crammed a couple of activities into a single trip to Europe. One of them required to stop for a couple of days in Munich. As I was looking for community events around the same time I noticed that the GDG Cloud chapter might have something coming up. Hence I reached out to Yevgen to see whether there would be a possibility of a meetup around that time, and in case there could be interest in a certain topic. Turns out that both requests aligned nicely and in consequence I was invited to speak at their hybrid event in June. The topic was \"State of GCP: .NET Edition\" which covers a range of products and services offered by Google Cloud Platform (GCP) that can be used in combination with Microsoft .NET technology stack, in particular with C#. Why C# and Google Cloud? Hang on, C# and Google? Personally, it allows me to leverage my existing knowledge and doesn't require me to dig deeper into other programming languages like JavaScript, Go, Python, and so forth, in order to benefit from Google Cloud, and it also shows that one isn't locked in to cloud computing provided by Microsoft Azure while using C# applications. Bearing this in mind, it's actually a solid way to build so-called cross-cloud or multi-cloud solutions. Addressing Misconceptions & Starting with .NET I was given approximately one hour to present about .NET. After setting up the system and being introduced by Yevgen to the in-person and online audience I started to address a couple of potential misconceptions regarding Microsoft .NET and C#. The majority of the audience had never used C# before and I was curious about some of the reasons. This then allowed me to go with the flow and clarify some statements. I added up a few practical examples from my experience and then bridged the discussion to Google Cloud services that can be used as part of the solution. Again sharing some of the obstacles and pitfalls observed and handled in the process. Some of the questions asked indicated that there is quite some work to do removing a slightly negative reputation of the Microsoft .NET eco-system, simply because it's Microsoft (maybe its past?), whereas other questions looked behind that facade and brought up comparison to other existing approaches like backend development with Node.js and Express, and how C# could be useful for such backend services. To begin with .NET development I explained the dotnet CLI tool and how project templates get you started. dotnet new list The important part is the installation of the Google.Cloud.Functions.Templates into your dotnet environment. Minimal APIs & NuGet in VS Code When I launched the terminal in Visual Studio Code and started to show how a sample API application is developed in C#, using Minimal APIs, the interest level in the audience rose instantly. I showed the generated source code and how easy it is to get going right away. Some remarks were like \"hey, that really looks like Express\", \"Oh, are those arrow functions?\" (meaning anonymous delegates), and \"that looks neat and clean\". var builder = WebApplication.CreateBuilder(args); var app = builder.Build(); app.MapGet(\"/\", () => \"Hello from .NET on Google Cloud!\"); app.Run(); Next, I ventured into the NuGet package management and how developers can integrate any package into their project seamlessly. Especially showing the over 700 NuGet packages available to use Google APIs for .NET for all kinds of services and functionalities. Also mentioning that the package management is less space-consuming and easier to maintain compared to Node modules. Lastly, it was time to deploy to Google Cloud. All this time I stayed in VS Code and never had to switch focus to another application. The Cloud Code extension for VS Code provides you access to your resources in the cloud, and using the gcloud CLI tool does the rest of the job. Deploying the newly created API service to App Engine Flexible was done quickly. Serverless with Google Cloud Functions What about serverless? Let's do it. I explained that the dotnet CLI tool can be extended with project templates and showed that there are project templates targeting Google Cloud Functions. # Install Google Cloud Function templates dotnet new install Google.Cloud.Functions.Templates First, I created a new Google Cloud function using the HTTP endpoint trigger. # Create an HTTP Function dotnet new gcf-http -n DevFest Showed the source code and the necessary implementation of the interface, renamed the entry function to Greeting. using Google.Cloud.Functions.Framework; using Microsoft.AspNetCore.Http; using System.Threading.Tasks; namespace DevFest; public class Greeting : IHttpFunction { /// <summary> /// Logic for your function goes here. /// </summary> /// <param name=\"context\">The HTTP context, containing the request and the response.</param> /// <returns>A task representing the asynchronous operation.</returns> public async Task HandleAsync(HttpContext context) { await context.Response.WriteAsync(\"Hello, Functions Framework.\", context.RequestAborted); } } And how to deploy it using the gcloud CLI tool. # Deploy via gcloud CLI gcloud functions deploy my-http-function \\ --region=us-central1 \\ --gen2 \\ --runtime=dotnet10 \\ --source=. \\ --entry-point=DevFest.Greeting \\ --trigger-http \\ --memory=128M \\ --max-instances=5 \\ --allow-unauthenticated With an active Cloud billing account, enabled APIs - Cloud Functions and Artifact Registry - as well as local gcloud authentication, it's roughly a matter of five minutes from start to finish. If not sure, you can retrieve a list of available runtimes in a region like this: gcloud functions runtimes list --region=us-central1 Then, I did the same using the project template for an event-triggered Google Cloud function. dotnet new gcf-event -n DevFest And explained the flexibility of the type-induced interface to create functions handling different types of data loads. The default type is StorageObjectData which I changed to MessagePublishedData to explain how a function can be used to handle a Pub/Sub message. And thanks to the Google Cloud Functions Framework and local hosting, you can launch, test, and debug your code before deploying it to Google Cloud. dotnet run Conclusion & Wrap-Up Gratefully, with every piece of source code shown and sharing my experience of combining C# implementation using NuGet packages and how to deploy it to GCP there were more and more questions in the audience. Finally, the team at GDG Cloud Munich signalled that we were running out of time, and I wrapped it up quickly with my opinionated conclusion of using C# together with the rich .NET eco-system in order to develop scalable, enterprise-ready, and cloud-native solutions using Google Cloud Platform. I was humbled by the audience's interest to know more and the numerous follow-up questions regarding the talk and the live demos shown. Thanks! My heart-felt thanks and best wishes to the organising team of GDG Cloud Munich, namely Yevgen Batovskyi and Spyros Kyriazatis, for this opportunity to talk and share my passion. It was an amazing evening at the Google office in Munich. PS: Apart from the announcement screen the whole talk was a no slide deck presentation purely based on me talking about the history, the evolution, the current state of C# and .NET, and how it fares together with GCP based on my experience. References & Links GDG Cloud Munich Community Google Cloud for .NET Developers Google Cloud Functions .NET Framework on GitHub Google Cloud Client Libraries for .NET on NuGet Cloud Code for Visual Studio Code TIOBE Index Join the Conversation Have you experimented with deploying C# and .NET workloads to Google Cloud Platform or building serverless event handlers with Google Cloud Functions? What was your experience integrating NuGet packages with Google Cloud services outside of Azure? Feel free to connect and share your thoughts with me on X (@JKirstaetter), BlueSky (@jochen.kirstaetter.name), or Mastodon (@JKirstaetter). You can also subscribe to my blog's RSS feed for upcoming articles and technical write-ups. Picture credits: Mary Jane Kirstätter and Inna Zaytseva if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'gdg-cloud-munich.html'; this.page.identifier = '9271ed1c97_idgdg-cloud-munich'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "gegen-die-klte-hilft-nur-ein-heies-bad.html": {
    "href": "gegen-die-klte-hilft-nur-ein-heies-bad.html",
    "title": "Gegen die Kälte hilft nur ein heißes Bad - Get Blogged by JoKi",
    "summary": "Gegen die Kälte hilft nur ein heißes Bad Copy Markdown View Markdown GitHub Markdown Source Gegen die Kälte hilft nur ein heißes Bad Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gegen-die-klte-hilft-nur-ein-heies-bad'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gegen-die-klte-hilft-nur-ein-heies-bad'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Gegen die Kälte hilft nur ein heißes Bad') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-18 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Also, ich weiß ja nicht, wie es euch geht, aber die kalte Jahreszeit ist nicht wirklich etwas für mich. Oder anders formuliert, wenn es denn wenigstens durchgehend gleich kalt, trocken und mit weißen Landschaften wäre, dann könnte ich mich daran erwärmen... Aber, was haben wir - Aprilwetter im Winter. Brrrr! Image courtesy of Pengfei Liu if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'gegen-die-klte-hilft-nur-ein-heies-bad.html'; this.page.identifier = '9271ed1c97_idgegen-die-klte-hilft-nur-ein-heies-bad'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "gemini-responseschema.html": {
    "href": "gemini-responseschema.html",
    "title": "Gemini ResponseSchema vs JSON Schema specs of \"type\" as array - Get Blogged by JoKi",
    "summary": "Gemini ResponseSchema vs JSON Schema specs of \"type\" as array Copy Markdown View Markdown GitHub Markdown Source Gemini ResponseSchema vs JSON Schema specs of \"type\" as array Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gemini-responseschema'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gemini-responseschema'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Gemini ResponseSchema vs JSON Schema specs of \"type\" as array') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2025-01-20 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Recently, while developing some changes in the Gemini SDK for .NET and given a reported issue on GitHub I had a look at the possibilities of passing an object as the expected structured output in JSON format in order to get the response from the Gemini API. Turns out it's not trivial and there are several options available, each with of their own obstacles. Let's have a look... JSON schema exporter in .NET 9 First, I came across a new feature in .NET 9 - JSON schema exporter. The JsonSchemaExporter class, introduced in .NET 9, lets you extract JSON schema documents from .NET types using either a JsonSerializerOptions or JsonTypeInfo instance. The resultant schema provides a specification of the JSON serialization contract for the .NET type. The schema describes the shape of what would be serialized and what can be deserialized. Although interesting, it's too new and the Gemini SDK for .NET needs support for previous versions of .NET including .NET Framework. So, not an option yet. NJsonSchema by Rico Suter Next, I found the NJsonSchema library and gave it a try. The library is well written and actively maintained. Also, the package supports .NET Framework 4.6.2 or higher, .NET Standard 2.0, and .NET 6.0 or higher. Sounds like the perfect candidate to integrate into my SDK. However, I noticed that there are slight differences between the available documentation and the latest version 11.x of the NuGet package. Using the previous, latest version 10.x of the package solved that. Unfortunately, I ran into the problem described below regarding the generation of type keys as arrays of principle types, and I couldn't find a solution to output a single string value (as expected by the Gemini API). The latest version seems to migrate from Newtonsoft.Json to System.Text.Json but that process has not been completed yet. Throughout the Gemini SDK for .NET I'm using System.Text.Json everywhere as much as possible. I think the test projects still use Newtonsoft.Jsonindirectly somehow. Anyway, I didn't like adding another JSON library to the SDK and I couldn't resolve the described issue with the type array, so not an option. json-everything by Greg Dennis Lastly, I came across the json-everything library which generated the JSON schema as expected by the Gemini API. The NuGet package is compatible with .NET Standard 2.0 (which works for .NET Framework 4.7.1 or higher, IIRC) as well as .NET 8.0 or higher. As I ditched support for .NET 6.0 recently in the SDK it's a match. Serialization of .NET types to JSON and JSON Yes, you read this correctly. Most commonly the Gemini SDK for .NET has to serialize the payload for the REST API endpoint requests. That's regular stuff and nothing special about it using System.Text.Json. /// <summary> /// Return serialized JSON string of request payload. /// </summary> /// <param name=\"request\"></param> /// <returns></returns> protected string Serialize<T>(T request) { return JsonSerializer.Serialize(request, _options); } The \"usual\" method to serialize to JSON in .NET However, to generate a structured output one has to provide a JSON Schema describing the object used for the response. JSON schema uses a defined syntax to describe objects which is different from a regular object serialization. Hence the need to integrate one of the previously mentioned libraries in order to be generate a JSON schema based on a .NET type. One of the solutions is to implement a specialized JSON converter and attribute the property to use it. Here's how it's done in the SDK. /// <summary> /// Optional. Output response schema of the generated candidate text when response mime type can have schema. /// </summary> /// <remarks> /// Schema can be objects, primitives or arrays and is a subset of [OpenAPI schema](https://spec.openapis.org/oas/v3.0.3#schema). /// If set, a compatible response_mime_type must also be set. Compatible mimetypes: `application/json`: Schema for JSON response. /// </remarks> [JsonConverter(typeof(ResponseSchemaJsonConverter))] public object? ResponseSchema { get; set; } Assign a custom JSON converter to the property A custom converter needs to overwrite two methods - Read and Write. See further details about How to write custom converters for JSON serialization (marshalling) in .NET. Here's the current implementation (kindly ignore Read as its not used). #if NET472_OR_GREATER || NETSTANDARD2_0 using System; using System.Text.Json; using System.Text.Json.Serialization; #endif using Json.Schema; using Json.Schema.Generation; namespace Mscc.GenerativeAI { /// <summary> /// Custom JSON converter to serialize and deserialize JSON schema. /// </summary> public sealed class ResponseSchemaJsonConverter : JsonConverter<object> { /// <inheritdoc cref=\"JsonConverter\"/> public override object? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return JsonSerializer.Deserialize<dynamic>(reader.GetString()!, options); } /// <inheritdoc cref=\"JsonConverter\"/> public override void Write( Utf8JsonWriter writer, object value, JsonSerializerOptions options) { var type = value.GetType(); // How to figure out: type vs anonymous vs dynamic? if (type.Name.Substring(0,Math.Min(type.Name.Length, 20)).Contains(\"AnonymousType\")) { var newOptions = new JsonSerializerOptions(options); newOptions.Converters.Remove(this); JsonSerializer.Serialize(writer, value, value.GetType(), newOptions); } else { var config = new SchemaGeneratorConfiguration() { PropertyNameResolver = PropertyNameResolvers.CamelCase }; var schemaBuilder = new JsonSchemaBuilder(); var schema = schemaBuilder.FromType(type, config).Build(); JsonSerializer.Serialize(writer, schema, schema.GetType(), options); } } } } Custom JSON Converter class to generate JSON schema output As you see, there's still of room to improve the implementation. For once, I'm struggling with the serialization of types from the System.Dynamic namespace, eg. ExpandoObject. Why? Because this generates (again) additional JSON keys like readOnly which are rejected. Right now, it's not clear to me whether it's a specs problem against JSON Schema or an incompatibility on the side of the Gemini API. Note: The boolean keywords readOnlyand writeOnly are part of the JSON Schema Annotations and have been added in draft 7. Feel free to drop a note or hint in the article comments below. Or create a PR in the repository on GitHub. Following is the post I published on the Build with Google AI Forum: Incompatibility issue in the Gemini API regarding JSON Schema I'm facing an issue regarding the generation of JSON Schema used as value of GenerationConfig.ResponseSchema given the following scenario. The class is defined like this class Recipe { public string Name { get; set; } } Which is then passed into the property as a list / array in order to retrieve multiple suggestions from the Gemini API. var generationConfig = new GenerationConfig() { ResponseMimeType = \"application/json\", ResponseSchema = new List<Recipe>() }; The generated output looks like this { \"model\" : \"models/gemini-1.5-pro-latest\", \"contents\" : [ { \"role\" : \"user\", \"parts\" : [ { \"text\" : \"List a few popular cookie recipes.\" } ] } ], \"generationConfig\" : { \"responseMimeType\" : \"application/json\", \"responseSchema\" : { \"type\" : \"array\", \"items\" : { \"type\" : \"object\", \"properties\" : { \"name\" : { \"type\" : [ \"string\", \"null\" ] } }, \"required\" : [ \"name\" ] } } } } Specifying the property Name as a nullable string. This is conform with the specs of type according to https://json-schema.org/understanding-json-schema/reference/type allowing instances that can be of multiple primitive types. The type keyword may either be a string or an array: ** If it's a string, it is the name of one of the basic types above.* ** If it is an array, it must be an array of strings, where each string is the name of one of the basic types, and each element is unique. In this case, the JSON snippet is valid if it matches any of the given types.* See also https://json-schema.org/draft/2020-12/json-schema-core#section-7.6.1 and following paragraphs. Currently, the Gemini API returns an HTTP 400 Bad Request with this information. { \"error\": { \"code\": 400, \"message\": \"Invalid JSON payload received. Unknown name \\\"type\\\" at 'generation_config.response_schema.items.properties[0].value': Proto field is not repeating, cannot start list.\\nInvalid JSON payload received. Unknown name \\\"type\\\" at 'generation_config.response_schema.items.properties[1].value': Proto field is not repeating, cannot start list.\", \"status\": \"INVALID_ARGUMENT\", \"details\": [ { \"@type\": \"type.googleapis.com/google.rpc.BadRequest\", \"fieldViolations\": [ { \"field\": \"generation_config.response_schema.items.properties[0].value\", \"description\": \"Invalid JSON payload received. Unknown name \\\"type\\\" at 'generation_config.response_schema.items.properties[0].value': Proto field is not repeating, cannot start list.\" }, { \"field\": \"generation_config.response_schema.items.properties[1].value\", \"description\": \"Invalid JSON payload received. Unknown name \\\"type\\\" at 'generation_config.response_schema.items.properties[1].value': Proto field is not repeating, cannot start list.\" } ] } ] } } The error message essentially showing \"type\": Proto field is not repeating, cannot start list. And therefor indicating that the value of type cannot be a list/array of primitives types. Adding to this observation on the API side, using the same response schema in AIS gives me the following error message on Save. Sorry to say but this seems to be a short-coming on Gemini's side regarding the interpretation of JSON Schema specifications. Similarly, I'm getting HTTP 400 Bad Request responses when other keys, like eg. \"readOnly\" and others are used in the schema definition. This doesn't align with the specs of JSON Schema. Changing the type value to a single primitive type returns an HTTP 200 OK. { \"model\" : \"models/gemini-1.5-pro-latest\", \"contents\" : [ { \"role\" : \"user\", \"parts\" : [ { \"text\" : \"List a few popular cookie recipes.\" } ] } ], \"generationConfig\" : { \"responseMimeType\" : \"application/json\", \"responseSchema\" : { \"type\" : \"array\", \"items\" : { \"type\" : \"object\", \"properties\" : { \"name\" : { \"type\" : \"string\" } } } } } } and the result as expected. { \"candidates\" : [ { \"content\" : { \"parts\" : [ { \"text\" : \"[{\\\"name\\\": \\\"Chocolate Chip Cookies\\\"}, {\\\"name\\\": \\\"Peanut Butter Cookies\\\"}, {\\\"name\\\": \\\"Oatmeal Raisin Cookies\\\"}, {\\\"name\\\": \\\"Snickerdoodles\\\"}, {\\\"name\\\": \\\"Shortbread Cookies\\\"}]\" } ], \"role\" : \"model\" }, \"finishReason\" : \"STOP\", \"avgLogprobs\" : -0.009499770402908326 } ], \"usageMetadata\" : { \"promptTokenCount\" : 8, \"candidatesTokenCount\" : 45, \"totalTokenCount\" : 53 }, \"modelVersion\" : \"gemini-1.5-pro-002\" } Someone else observing this issue? Right now, this seems to be an issue with the Gemini API not accepting a conforming JSON Schema with multiple primitive types as the type definition of a property. Assistance please. Thanks. Image courtesy of Jochen Kirstätter if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'gemini-responseschema.html'; this.page.identifier = '9271ed1c97_idgemini-responseschema'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "gemma-3-missing-features-despite-announcement.html": {
    "href": "gemma-3-missing-features-despite-announcement.html",
    "title": "Gemma 3 - missing features despite announcement - Get Blogged by JoKi",
    "summary": "Gemma 3 - missing features despite announcement Copy Markdown View Markdown GitHub Markdown Source Gemma 3 - missing features despite announcement Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gemma-3-missing-features-despite-announcement'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'gemma-3-missing-features-despite-announcement'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Gemma 3 - missing features despite announcement') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2025-03-12 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Congrats to the team to provide the new Gemma 3 models and the new endpoint on the Google AI API. The announcement blog - https://blog.google/technology/developers/gemma-3/ - reads wonderfully. Until someone puts it to the test... Create AI with advanced text and visual reasoning capabilities Easily build applications that analyze images, text, and short videos, opening up new possibilities for interactive and intelligent applications HTTP 400: \"Image input modality is not enabled for models/gemma-3-27b-it\" HTTP 400: \"Audio input modality is not enabled for models/gemma-3-27b-it\" Tried different images (png, jpg, bmp), video (mp4) and PDF documents - both via inlineData and per File API using fileData attributes. Create AI-driven workflows using function calling Gemma 3 supports function calling and structured output to help you automate tasks and build agentic experiences. HTTP 400: \"Function calling is not enabled for models/gemma-3-27b-it\" HTTP 400: \"Json mode is not enabled for models/gemma-3-27b-it\" HTTP 400: \"Enum mode is not enabled for models/gemma-3-27b-it\" System Instruction HTTP 400: \"Developer instruction is not enabled for models/gemma-3-27b-it\" Code execution? It's not explicitly mentioned in the blog. What's the situation here? Right now... HTTP 400: \"Code execution is not enabled for models/gemma-3-27b-it\" Candidate count > 1? It's not explicitly mentioned in the blog. What's the situation here? Right now... HTTP: \"Multiple candidates is not enabled for models/gemma-3-27b-it\" Not sure what's the issue is... However, with the announcement blog I would kind of expect that the mentioned features are available and operational from day 0 on. And not hoping for the best and then those are being added at a later stage. I'm not sure whether those features have been disabled for the gemma3 model in the Gemini API and the model itself might be capable to deal with everything, like when used locally or deployed in Vertex AI or Cloud Run with GPU... What are your observations? if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'gemma-3-missing-features-despite-announcement.html'; this.page.identifier = '9271ed1c97_idgemma-3-missing-features-despite-announcement'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "generate-qr-codes-gcf.html": {
    "href": "generate-qr-codes-gcf.html",
    "title": "Generating QR codes - the Easy, the Comfy and the Smarty - Get Blogged by JoKi",
    "summary": "Generating QR codes - the Easy, the Comfy and the Smarty Copy Markdown View Markdown GitHub Markdown Source Generating QR codes - the Easy, the Comfy and the Smarty Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'generate-qr-codes-gcf'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'generate-qr-codes-gcf'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Generating QR codes - the Easy, the Comfy and the Smarty') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-11-06 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Using QR codes to assist people to find the right information quickly is a good choice nowadays. A QR code can be scanned easily with any mobile camera application and get your visitors straight into the action where you want them to be. The Easy One of the fastest and easiest ways to get a QR code is to sign up to one of the many online sites offering it as a Software-as-a-Service (SaaS) model and hoping the best. I used to do that in the past but recently noticed that the available conditions of a free tier had been reduced massively, meaning I lost a good number of QR codes. Maybe more interestingly might be to check out the Google Charts API and to use their URL pattern to create your own QR codes quickly. You'll find that API under the section of Infographics. However, at the time of writing there is a small drawback. Using the API is based on an HTTP GET request with a few query string parameters. The result is ad hoc ready and usable. Feel free to scan it with your mobile camera app and visit the URL presented. One possible use case would be to integrate that URL template with a spreadsheet application like Sheets and have a column holding the hyperlink to the QR codes. Kindly read the description above for the chl parameter to keep an eye on the format of the payload data. This approach has been used twice for the annual MSCC Developers Conference in Mauritius and generated over 4,000 QR codes across two years. The individual QR codes were sent out as embedded image in emails to registrants and used to check in attendees during the event. The Comfy Now with Google's API deprecation warning in mind, it might be better to look for a more future-safe variation to generate QR codes. Interestingly, I was inspired by a post on LinkedIn for the initial solution: Write a small Python snippet to generate the QR code. Using Python import qrcode qr = qrcode.make(\"https://jochen.kirstaetter.name/\") qr.save(\"qrcode.png\") Python code snippet to generate a QR code Save that snippet as generateqrcode.py file and run it using the Python 3 interpreter, like so. $ python generateqrcode.py Executing the Python snippet to create a QR code This small program is going to create the QR code and save it as a PNG file in the same directory where you run it. In the most likely case that you are going to be greeted by an error regarding missing module you have to install it. ModuleNotFoundError: No module named 'qrcode' $ pip install qrcode Install the missing module qrcode using pip. However a little bit of comfort and flexibility would be most appreciated though. After a few iterations the Python snippet might be transformed to the following. # Duet AI: Generate a QR code from command-line argument # using the qrcode library # # ModuleNotFoundError: No module named 'qrcode' # $ pip install qrcode # Import the qrcode library import qrcode import sys # Check if the command line argument is provided if len(sys.argv) < 2: print(\"Usage: python3 \" + sys.argv[0] + \" <url> [<filename>]\") sys.exit(1) # Get the command line argument(s) url = sys.argv[1] filename = \"qrcode.png\" if len(sys.argv) == 3: filename = sys.argv[2] # Check if the image filename has an extension if not filename.endswith(\".png\"): filename += \".png\" # Create the QR code qr = qrcode.make(url) # Save the QR code to a file qr.save(filename) # Print a message to the user print(\"QR code saved to \" + filename) A more versatile Python snippet to generate a QR code The program reads the URL and the filename of the QR code to generate from the command line and has a bit of usage information. With this implementation it is possible to generate QR codes in a more versatile approach using the following command line statement. $ python generateqrcode.py https://jochen.kirstaetter.name/ getblogged.png Pass the payload data and file name to generate the QR code This approach allows you to generate any QR code offline on your local machine. Using a little bit of bash-Fu, aka shell scripting, one can write a small loop reading single lines from another file and feed them into this Python script to quickly produce any number of QR code images. One approach could be using the while read -r line; do COMMAND; done < input.file syntax. Probably combined with the xargs command to specify the URL for the QR data and the file name of the generated QR code image per line in the given input file. Avid readers of my blog might be wondering: But why did you use Python? As mentioned earlier, I got the initial idea from a post on LI and simply took the Python snippet as-is. This way it took me less than 15 minutes from reading to finished implementation. OK, I had minor help from my P-AI-R programming buddy Duet AI giving me the code fragments to read the command line arguments, to do the argument sanity checks and to deal with default values. Although I know Python in general I'm not familiar with it on a daily base. It is also possible to \"translate\" the implementation above to C#. Using C# Let's create a similar console application using .NET instead. Additionally, we use a NuGet package called QRCoder to generate the QR codes based on our payload. It's an open source library and hosted on GitHub. $ dotnet new console -o qrcode-csharp $ cd qrcode-csharp $ dotnet add package QRCoder Create a .NET console application and add QRCoder NuGet package Open the newly created file Program.cs with your favourite text editor and replace the existing code with the following. using QRCoder; using (QRCodeGenerator qrGenerator = new QRCodeGenerator()) using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(\"https://jochen.kirstaetter.name\", QRCodeGenerator.ECCLevel.Q)) { PngByteQRCode qrCode = new PngByteQRCode(qrCodeData); byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20); File.WriteAllBytes(\"qrcode.png\", qrCodeAsPngByteArr); } Console.WriteLine(\"QR code saved.\"); Save the file and launch the console application. We are using the new minimal approach of .NET 6. $ dotnet run QR code saved. If there are no typos and the project compiled without any errors you should see the generated QR code in the same directory. Here's the PNG image I created on my Chromebook using .NET 6. I leave the implementation to deal with command line arguments, default values and parameter checks to you. Or better said, the complete source code is available in my GitHub repository. The Smarty With the easy approach we saw that we are able to create QR codes online and integrate them into any kind of other resources online; however we are dependent on the circumstances that those resources continue to be available to our liking (which didn't happen to be the case for me previously). The comfortable approach was to have a command line tool that is capable to produce the images we would need; however it is limited to the local machine and lacks the previous level of integration. How about combining both ways in a smart fashion? Let's embrace cloud computing and more precisely serverless cloud computing. With minimal to no changes to the previous code snippet it is possible to create a Google Cloud Function (or Azure Function) and bring back the online integration with other applications. But how does this work? Google Cloud Function in Python First, create a new directory on your machine and create a file called main.py in which we copy the Python code from the previous command line script. Using that file name is by convention for Google Cloud Functions. # Duet AI: Create a Cloud Function in Python that accepts # a URL as a query string parameter and returns the generated # QR code as a PNG image. # Import the necessary modules import functions_framework import qrcode import base64 import io from io import BytesIO # Define the function that will be executed by the Cloud Function @functions_framework.http def generate_qrcode(request): # For more information about CORS and CORS preflight requests, see: # https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request # Set CORS headers for the preflight request if request.method == \"OPTIONS\": # Allows GET requests from any origin with the Content-Type # header and caches preflight response for an 3600s headers = { \"Access-Control-Allow-Origin\": \"*\", \"Access-Control-Allow-Methods\": \"GET\", \"Access-Control-Allow-Headers\": \"Content-Type\", \"Access-Control-Max-Age\": \"3600\", } return (\"\", 204, headers) # Set CORS headers and mime-type for the main request headers = { \"Access-Control-Allow-Origin\": \"*\", \"Content-Type\": \"image/png\" } # Get the URL from the query string parameter url = request.args.get('url') # Check if the URL is valid if not url: return 'Missing URL parameter', 400 # Generate the QR code #qr = qrcode.make(url) # Convert the QR code to a PNG image #img = qr.make_image() # Generate the QR code qr = qrcode.QRCode( version=1, error_correction=qrcode.constants.ERROR_CORRECT_L, box_size=10, border=4, ) qr.add_data(url) qr.make(fit=True) img = qr.make_image(fill_color=\"black\", back_color=\"white\") img_byte_arr = io.BytesIO() img.save(img_byte_arr) img_byte_arr = img_byte_arr.getvalue() return (img_byte_arr, 201, headers) The main.py file of Cloud Functions (GCF) The implementation of the Cloud Function in Python is according to the console application. Main difference here is that we do not handle command-line arguments but read the payload from a query string parameter called url and we return the generated PNG image in the HTTP response using the correct mime-type. To import additional Python modules into the Cloud function it is necessary to create a file called requirements.txt in the same folder where the main.py file resides. Enter the following content and save it. functions-framework==3.* google-cloud-error-reporting==1.9.1 qrcode==7.* Specify required modules for the Python function to run Before deployment to Google Cloud Functions it is recommended to run, test and debug your code locally. Use the Functions Framework to run your function without deploying. If not already installed, run the following command to install. $ pip install functions-framework Install Google's Functions Framework in Python Then you can launch it using this command. $ functions-framework --target generate_qrcode Run a Cloud Function locally listening on port 8080 Next, open a browser tab or window and navigate to the URL http://localhost:8080/. In case that you prefer staying in the terminal you can use curl to call same URL. You see the following HTTP 400 response: Missing URL parameter. Which is expected given our implementation of the function checking for the required query string parameter url. To generate an actual QR code image you have to provide information in the required query string parameter: http://localhost:8080/?url=https://jochen.kirstaetter.name/ You can use Cltr+C to stop the local function execution. Alternatively, you might give the Functions Emulator a try. At the time of writing is was tagged as Preview feature. Due to the lack of Docker environment on my machine I didn't try this. The command-line to deploy the Python function locally using the Functions Emulator would be like this. $ gcloud alpha functions local deploy generate_qrcode \\ --entry-point=generate_qrcode \\ --runtime=python Use Functions Emulator to deploy a Cloud Function locally Note: Use the Functions Emulator at your own risk! Finally, it is time to deploy the function to Google Cloud Functions. There are multiple options available. You could log into the Cloud Console and navigate to the Cloud Functions area and create a new function in the browser, or you have the gcloud SDK already installed and use the following command. $ gcloud functions deploy generate_qrcode \\ --gen2 \\ --source=. \\ --entry-point=generate_qrcode \\ --runtime=python311 \\ --region=us-east5 \\ --trigger-http \\ --allow-unauthenticated \\ --memory=128Mi \\ --max-instances=5 Deploy a Cloud Function using the gcloud command-line tool See deploying the function in the official documentation for more details. Using a 2nd gen Cloud Function is recommended. Those are built on Cloud Run and EventArc to provide an enhanced feature set. See Cloud Function version comparison for more information and explanation. You can optionally use the --allow-unauthenticated flag to reach the function without authentication. This is useful for testing, but we don't recommend using this setting in production unless you are creating a public API or website. After successful deployment you should have a working instance of your Cloud Function in the Console. It will also show you its URL to use the function. You can also query the so-called description of your deployed Cloud Function using the gcloud command. This will give you the uri address to reach the function. $ gcloud functions describe generate_code Alternatively, there is also the Google Cloud extension for Visual Studio Code that provides you access to your Cloud Functions to access and deploy them. Again, you can test your deployed Cloud Function at the given URL. Given identical payload in the url query string parameter the generated PNG image is showing the same QR code as your locally run function earlier. Google Cloud Function in C# The steps are similar to the previous ones however we are going to use the project template for Google Cloud Functions to start a new project using the dotnet CLI tool. $ dotnet new gcf-http -o QrCodeFunctionCsharp $ cd QrCodeFunctionCsharp $ dotnet add package QRCoder $ code . -r Create a new Google Cloud Function with HTTP trigger In case you don't have the project template you need to install it. See here for more information: The .NET Runtime $ dotnet new install Google.Cloud.Functions.Templates Install the project templates for Cloud Functions Replace the content of the newly created Program.cs with the following implementation. using Google.Cloud.Functions.Framework; using Microsoft.AspNetCore.Http; using QRCoder; using System.Threading.Tasks; namespace QrCodeFunctionCsharp; public class GenerateQrCode : IHttpFunction { /// <summary> /// Generates a QR code and writes the PNG image to the response. /// </summary> /// <param name=\"context\">The HTTP context, containing the request and the response.</param> /// <returns>The generated QR code as PNG image.</returns> public async Task HandleAsync(HttpContext context) { /// Set CORS headers for the preflight request /// https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request if (context.Request.Method == \"OPTIONS\") { /// Allows GET requests from any origin with the Content-Type /// header and caches preflight response for an 3600s context.Response.Headers.Add(\"Access-Control-Allow-Origin\", \"*\"); context.Response.Headers.Add(\"Access-Control-Allow-Methods\", \"GET\"); context.Response.Headers.Add(\"Access-Control-Allow-Headers\", \"Content-Type\"); context.Response.Headers.Add(\"Access-Control-Max-Age\", \"3600\"); context.Response.StatusCode = 204; return; } /// read query string string url = context.Request.Query[\"url\"]; /// check url parameter if (string.IsNullOrEmpty(url)) { context.Response.StatusCode = 400; await context.Response.WriteAsync(\"Missing url parameter.\"); return; } /// generate QR code using (QRCodeGenerator qrGenerator = new QRCodeGenerator()) using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(url, QRCodeGenerator.ECCLevel.Q)) { PngByteQRCode qrCode = new PngByteQRCode(qrCodeData); byte[] qrCodeAsPngByteArr = qrCode.GetGraphic(20); /// return byte array as response context.Response.Headers.Add(\"Access-Control-Allow-Origin\", \"*\"); context.Response.ContentType = \"image/png\"; context.Response.StatusCode = 201; await context.Response.Body.WriteAsync(qrCodeAsPngByteArr, 0, qrCodeAsPngByteArr.Length); await context.Response.Body.FlushAsync(); } } } Cloud Function to generate and respond a QR code as PNG image To test your function launch the project. $ dotnet run 2023-11-06T08:12:53.637Z [Google.Cloud.Functions.Hosting.EntryPoint] [info] Serving function QrCodeFunctionCsharp.GenerateQrCode 2023-11-06T08:12:53.665Z [Microsoft.Hosting.Lifetime] [info] Now listening on: http://127.0.0.1:8080 2023-11-06T08:12:53.666Z [Microsoft.Hosting.Lifetime] [info] Application started. Press Ctrl+C to shut down. 2023-11-06T08:12:53.666Z [Microsoft.Hosting.Lifetime] [info] Hosting environment: Production 2023-11-06T08:12:53.666Z [Microsoft.Hosting.Lifetime] [info] Content root path: /.../QrCodeFunctionCsharp Run the .NET function locally Either navigate your browser or use curl to access the URL http://localhost:8080/?url=https://jochen.kirstaetter.name/ to get the generated QR code as PNG image. Finally, deploy the .NET project to Google Cloud Functions via the gcloud CLI tool using the following command. $ gcloud functions deploy generate_qrcode-csharp \\ --gen2 \\ --source=. \\ --entry-point=QrCodeFunctionCsharp.GenerateQrCode \\ --runtime=dotnet6 \\ --trigger-http \\ --allow-unauthenticated \\ --memory=128Mi \\ --max-instances=5 Deploy a Cloud Function using the gcloud command-line tool The value of the entry-point flag is based on the used namespace and type (class) you defined in your project. See also the output of dotnet run regarding the EntryPoint information. At the time of writing, the two possible values for the runtime flag were dotnet3 and dotnet6 only. I hope that with the launch of .NET 8 mid of November that a new runtime dotnet8 for Cloud Functions will be available. Same recommendations as described in the paragraph on Python-based deployment apply. See above for details. Conclusion There is no excuse of not generating and using QR codes after you reached this far. As mentioned, we use them to provide unique images for our event registrations which we send out by email. Any registrant coming simply shows us her/his QR code and we are able to check them in quickly. It's a real time-saver. Please take into consideration that the code snippets in Python and C# above are merely a proof of concept although a powerful and production-ready one. Both libraries qrcode and QRCoder offer a ton of features to create more sophisticated QR codes. You can customise your codes with a branded centre image, use different error correction levels, dimensions and colours. I sincerely encourage you to explore those options in the official documentation of each package. Furthermore, having your own QR code generator online provides independence from any service provider or risk of deprecation. Actually with the described functionality you could start your own QR code Software-as-a-Service product and offer valuable features to others. Combining this blue print with a URL shortener with redirect feature to gather statistics on usage, a user authentication to isolate customers' instances of the Cloud Function from each other, and providing an appealing UX/UI website offering a management of QR code payloads. Spice it up with a landing page and affordable pricing structure which could potentially kick off a new business. Hmm, maybe more on that in the future... Image generated using Bing Image Creator: \"A fleet of QR codes in the clouds\" if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'generate-qr-codes-gcf.html'; this.page.identifier = '9271ed1c97_idgenerate-qr-codes-gcf'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "genius-is-indeed-a-heavy-burden.html": {
    "href": "genius-is-indeed-a-heavy-burden.html",
    "title": "Genius is indeed a heavy burden - Get Blogged by JoKi",
    "summary": "Genius is indeed a heavy burden Copy Markdown View Markdown GitHub Markdown Source Genius is indeed a heavy burden Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'genius-is-indeed-a-heavy-burden'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'genius-is-indeed-a-heavy-burden'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Genius is indeed a heavy burden') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-06-12 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Well, that's right... http://joecartoon.com/ Have fun with Joe if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'genius-is-indeed-a-heavy-burden.html'; this.page.identifier = '9271ed1c97_idgenius-is-indeed-a-heavy-burden'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "geocaching-casela-bird-park.html": {
    "href": "geocaching-casela-bird-park.html",
    "title": "Geocaching - Casela Bird Park (GC1BPF1) - Get Blogged by JoKi",
    "summary": "Geocaching - Casela Bird Park (GC1BPF1) Copy Markdown View Markdown GitHub Markdown Source Geocaching - Casela Bird Park (GC1BPF1) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'geocaching-casela-bird-park'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'geocaching-casela-bird-park'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Geocaching - Casela Bird Park (GC1BPF1)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-01-26 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Usually on Saturdays, we are on tour somewhere here on the island and trying hard to kill our time until mummy is done with her customers. But today we, or better I decided to take the children on our first real treasure hunt. Of course, the kids were super-excited about this. Due to the lack of a real treasure map and proper pirate outfit, I explained them that in modern times hunters are equipped with modern tools. What better to use than a tablet these days? So, the spark of excitement has been lit and off we went to one of our favourite locations on the island. Same like last Saturday, we checked out the playground first, then it was time for serious 'business'... Using the Geocaching client c:geo I got the coordinates of the nearby hidden cache. It is approximately 125 meters away from the entrance towards the restaurant. Interestingly, we got on the wrong track first as we didn't take care of the altitude... We ended up above the cache and had to go almost all the way back to reach the lower path near the ponds. With the help of the integrated compass the children approached the hidden place with good accuracy and patience. They immediately realized when they got off the directions. Actually, it was very impressive for me to watch them handling the tablet, discussing the direction amongst them and using the compass needle to navigate. As it was our first cache ever it wasn't clear what to expect but after all the micro cache is clearly to identify and the astonishment for both was high. There was even a coin in. Unfortunately, I couldn't figure out what the currency was. Maybe someone else can tell. Anyways, we left an item in the cache and the children took care of that it is well hidden again. Of course, as good modern pirates we made sure that no one else watched us... Totally happy about their find we talked about future treasures here in Mauritius. I guess that this one wasn't the last one for sure. More reports coming up soon, too. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'geocaching-casela-bird-park.html'; this.page.identifier = '9271ed1c97_idgeocaching-casela-bird-park'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "geocaching-world-wide-treasure-hunt.html": {
    "href": "geocaching-world-wide-treasure-hunt.html",
    "title": "Geocaching - World wide treasure hunt - Get Blogged by JoKi",
    "summary": "Geocaching - World wide treasure hunt Copy Markdown View Markdown GitHub Markdown Source Geocaching - World wide treasure hunt Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'geocaching-world-wide-treasure-hunt'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'geocaching-world-wide-treasure-hunt'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Geocaching - World wide treasure hunt') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-12-07 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); I'm not quite sure how I came across this topic but actually I find it absolutely interesting, challenging and most of all a great fun for the family and friends. The interesting part is for sure that you can follow other peoples treasures and their preferred locations where a cache might be hidden. Of course, it wont be easy to find a cache after all. Sometimes there are even 'mystery caches' which have either riddles, further instructions or little brain games for you in order to find the actual cache - that's the challenge. And last but not least, those caches are hidden outdoor. A great experience to explore nature either on your own, or your family especially with children, or as a treasure hunting pack with a couple of friends. What is geocaching? It's a high-tech outdoor treasure hunting game that's a great way to explore the world with friends, family or on your own. Participants use GPS-enabled devices to locate hidden containers called geocaches. There are over one million geocaches hidden around the world today, waiting for you to find them. Visit Geocaching.com to search for geocaches near you. (Source: Referral Email of geocaching.com) Checkout the Geocaching 101 for further details and information. They also provide a video channel on YouTube. Which equipment do I need? Any GPS-enabled device is sufficient to go onto the hunt. I'm going to start our geocaching experience equipped with my Samsung Galaxy Tab. Additionally, I installed a geocaching.com client called c:geo that hopefully assists me soon. Combined with a map app like Google Maps and a nice Compass app you should be fully equipped and ready to go. I guess, that even a car navigation system is perfect for that task. Later on, with more experience and demand for technology (or precision) it might be interesting to opt-in for a pure GPS device, like a Garmin or any other brand on the market. What is a geocache and what does it contain? In its simplest form, a cache always contains a logbook or logsheet for you to log your find. Larger caches may contain a logbook and any number of items. These items turn the adventure into a true treasure hunt. You never know what the cache owner or visitors to the cache may have left for you to enjoy. Remember, if you take something, leave something of equal or greater value in return. It is recommended that items in a cache be individually packaged in a clear, zipped plastic bag to protect them from the elements. Finding your first geocache Well, first you have to have interest to pick up the challenge. Then you have to check out the Geocache directory on geocaching.com. They have recommendations for beginner's caches but you are free to choose any. Actually, we have a Mystery Cache very close to our base, and I guess that we are going for that one on our first trip. Anyway, there is a very informative guide on the website which should answer all your questions about starting your new outdoor adventure. For sure, it's going to be rewarding. Team up with friends and family Especially as a beginner there might be misunderstandings in handling the GPS coordinates, the compass, or the map, and even finding the container at the documented position isn't easy in the first place. Luckily, there are logbook reports online from other hunters, and most of the time there are even 'spoiler' images available. But also bear in mind, that a geocache might have been removed or is lost due to unconscious people or whatever other reasons. Don't be disappointed in case that you can't find anything... There be nothing anymore. A general recommendation in this case would be to replace the missing container with a new one, and give feedback to the original owner about the state of that particular location. After all, it's about fun and active participation in a world-wide community. Geocaches in Mauritius? Yes, there are currently about 45 geocaches spread all over the island, and even a single one in Rodrigues - that's gonna be a tough one. Hopefully, we will get increasing numbers as Geocaching.com allows, no better, even encourages you to hide new containers at your locations of choice. I think this is going to be real fun for us during the upcoming weeks and months. Especially, when we are travelling to other countries and transfer so-called trackable items between geocaches. On my first impression, Geocaching.com seems to be very mature, open and community-oriented. There are literally hundreds of thousands geocache 'hunters' all over the world. And usually finding a container remote from your home is very rewarding. I'll keep you updated in these matters during the next months to come... Image credit: Florian Bernhard if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'geocaching-world-wide-treasure-hunt.html'; this.page.identifier = '9271ed1c97_idgeocaching-world-wide-treasure-hunt'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "getting-started-with-gemini-using-vs-http.html": {
    "href": "getting-started-with-gemini-using-vs-http.html",
    "title": "Getting started with Gemini using Visual Studio - Get Blogged by JoKi",
    "summary": "Getting started with Gemini using Visual Studio Copy Markdown View Markdown GitHub Markdown Source Getting started with Gemini using Visual Studio Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'getting-started-with-gemini-using-vs-http'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'getting-started-with-gemini-using-vs-http'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Getting started with Gemini using Visual Studio') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-02-22 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); With the release of Visual Studio 2022 Version 17.5 there is a new feature available that allows for better, faster API development using .http/.rest files with an integrated HTTP client. Those files enable you to \"run\" your API endpoints and manipulate various REST calls to iterate within parameters and see the outputs in a structured way. Access to Gemini is currently offered through SDKs for various programming languages like Python, Go, Node.js, and so forth, including the barebone REST API. You can get an overview here: https://ai.google.dev/tutorials The tutorial on Quickstart: Get started with Gemini using the REST API is limited to the use of the curl command only. If you want to quickly try out the Gemini API, you can use curl commands to call the methods in the REST API. The examples in this tutorial show calls for each API method. As a Windows developer using Visual Studio this might not be the optimum starting point, hence this article which calls the same REST API using .http/.rest files in Visual Studio. These .http/.rest files aren’t meant to replace integration and unit testing. Instead, they provide a new way to rapidly iterate on API development, as well as a common place to monitor the APIs your app may be using and investigate required inputs/outputs during development. Learn more about Visual Studio 2022 - 17.5 release announcement. Set up your API key To use the Gemini API, you'll need an API key. If you don't already have one, create a key in Google AI Studio. Get an API key In order to keep private, sensitive information and secrets out of your source code repositories, it is recommended to use either Environment Variables, User Secrets, or Azure Key Vault to retrieve data like an API key. Here, I'm going to create an .env file and place it into the project folder with the following content. API_KEY=<The generated Gemini API key> To access the value of a variable that is defined in a .env file, use $dotenv. More details are described under Environment Variables in the official documentation. All examples read the API key as environment variable from the .env file and stores it as a (local) variable. Additionally, there are variables for the API version and the model to be used. More information on API versions explained in the official documentation. Model info When starting with the Gemini API there is often the question of What's available? Apart from the documentation you can query the Gemini API to retrieve a list of available models, and you can get the details of an individual model. List models If you GET the models directory, it uses the list method to list all of the models available through the API, including both the Gemini and PaLM family models. @apiKey={{$dotenv API_KEY}} @version=v1beta # Get a list of available models and description GET https://generativelanguage.googleapis.com/{{version}}/models?key={{apiKey}} Get model If you GET a model's URL, the API uses the get method to return information about that model such as version, display name, input token limit, etc. @apiKey={{$dotenv API_KEY}} @version=v1beta @model=gemini-pro # Get information about the model, including default values GET https://generativelanguage.googleapis.com/{{version}}/models/{{model}}?key={{apiKey}} Get model details from Gemini API The response might look like this. Gemini and Content based APIs Text-only input (Gemini Pro) For text-only input use the gemini-pro model and call the generateContent method to generate a response from the model. Using the variable values a POST request is send to the Gemini API endpoint with the prompt specified in JSON format. @apiKey={{$dotenv API_KEY}} @version=v1beta @model=gemini-pro # Send a text-only request POST https://generativelanguage.googleapis.com/{{version}}/models/{{model}}:generateContent?key={{apiKey}} Content-Type: application/json { \"contents\": [{ \"parts\": [{ \"text\": \"Write a story about a magic backpack.\" }] }] } Calling the text-only API using gemini-pro model Text-and-image input (Gemini Pro Vision) If the input contains both text and image, use the gemini-pro-vision model. The following snippets help you build a request and send it to the REST API. @apiKey={{$dotenv API_KEY}} @version=v1beta @model=gemini-pro-vision # Send a text and image as input request. # Command Prompt base64 conversion used: certutil -encodehex -f \"scones.jpg\" \"output.txt\" 0x40000001 1>nul POST https://generativelanguage.googleapis.com/{{version}}/models/{{model}}:generateContent?key={{apiKey}} Content-Type: application/json { \"contents\":[ { \"parts\":[ { \"text\": \"What is this picture?\" }, { \"inline_data\": { \"mime_type\":\"image/jpeg\", \"data\": \"/9j/4AAQSkZ...GTWup/9k=\" } } ] } ] } Multimodal prompt with image analysis using gemini-pro-vision model The various elements of your input are defined as parts in your prompt. Like previously, the text part contains your actual prompt and an additional file like an image is added as base64-encoded inline data including its mime type. The data key has a base64 encoded representation of the image to inspect using the prompt. The picture shows a table with a white tablecloth. On the table are two cups of coffee, two small bowls, one filled with blueberries and the other with a silver spoon. There are also three scones with blueberries on the table. Next to the scones are some peony flowers. Here are the steps to get the sample image and encode it in base64. The image is available here. It has been resized to a width of 512px prior to base64 encoding. https://storage.googleapis.com/generativeai-downloads/images/scones.jpg On Windows use a Command Prompt with the certutil command. > certutil -encodehex -f \"scones.jpg\" \"output.txt\" 0x40000001 1>nul Whereas on Linux or macOS use the base64 command to encode the image. $ base64 -w0 scones.jpg > output.txt Note: At the time of writing .http/.rest files in Visual Studio 2022 do not provide a functionality to read external files directly. Count tokens When using long prompts, it might be useful to count tokens before sending any content to the model. @apiKey={{$dotenv API_KEY}} @version=v1beta @model=gemini-pro # Count tokens for a long prompt POST https://generativelanguage.googleapis.com/{{version}}/models/{{model}}:countTokens?key={{apiKey}} Content-Type: application/json { \"contents\": [{ \"parts\": [{ \"text\": \"Write an extended story about a magic backpack.\" }] }] } Multi-turn conversations (chat) Using Gemini, you can build freeform conversations across multiple turns. @apiKey={{$dotenv API_KEY}} @version=v1beta @model=gemini-pro # Using multi-turn conversations (chat) POST https://generativelanguage.googleapis.com/{{version}}/models/{{model}}:generateContent?key={{apiKey}} Content-Type: application/json { \"contents\": [ {\"role\": \"user\", \"parts\": [{ \"text\": \"Write the first line of a story about a magic backpack.\"}]}, {\"role\": \"model\", \"parts\": [{ \"text\": \"In the bustling city of Meadow brook, lived a young girl named Sophie. She was a bright and curious soul with an imaginative mind.\"}]}, {\"role\": \"user\", \"parts\": [{ \"text\": \"Can you set it in a quiet village in 1600s France?\"}]}, ] } Working with configuration parameters Every prompt you send to the model includes parameter values that control how the model generates a response. The model can generate different results for different parameter values. Learn more about model parameters. Also, you can use safety settings to adjust the likelihood of getting responses that may be considered harmful. By default, safety settings block content with medium and/or high probability of being unsafe content across all dimensions. Learn more about safety settings. The following example specifies values for all the parameters of the generateContent method. @apiKey={{$dotenv API_KEY}} @version=v1beta @model=gemini-pro # Send a text-only request with model parameters and safety settings # Ref: https://ai.google.dev/docs/concepts#model-parameters POST https://generativelanguage.googleapis.com/{{version}}/models/{{model}}:generateContent?key={{apiKey}} Content-Type: application/json { \"contents\": [{ \"parts\": [{ \"text\": \"Write a story about a magic backpack.\" }] }], \"safetySettings\": [ { \"category\": \"HARM_CATEGORY_DANGEROUS_CONTENT\", \"threshold\": \"BLOCK_ONLY_HIGH\" } ], \"generationConfig\": { \"stopSequences\": [ \"Title\" ], \"temperature\": 1.0, \"maxOutputTokens\": 800, \"topP\": 0.8, \"topK\": 10 } } Limited to Visual Studio? Absolutely not! I successfully used the same set of .http files in the following text editors and environments with the REST Client extension by Huachao Mao. Visual Studio Code - locally and online https://vscode.dev/ Google Cloud Shell Editor (Code OSS for the Web) Project IDX - https://idx.dev/ Source code: Gemini Playground I created a repository on GitHub gemini-playground that contains all samples described above with the exception of the .env file and the API key to access the Gemini REST API. Feel free to give it a star and fork it for your own purpose. Georestricted access to API of Gemini AI Unfortunately, if you are residing in a country that has no access to the API endpoints of Gemini AI you will get an HTTP 400 response like the following one. Lastly, a story about a magic backpack Here's the response I was provided by Gemini API. Once upon a time, in a bustling town by the forest's edge, lived a young boy named Alex. Alex was an inquisitive and adventurous child who loved exploring the woods and playing with his friends. However, he had a secret desire that no one knew about—he craved magic and adventure. One morning, as Alex wandered through the forest on his way to school, he stumbled upon a hidden glade. Fascinated by the serene atmosphere, he ventured deeper, following a trail of glowing fireflies. In the heart of the glade, beneath an ancient oak tree, lay a worn-out backpack. Curiosity compelling him, Alex reached out and touched it. As soon as his fingers brushed against the bag, it sprung to life. The backpack shimmered with brilliant colors, its straps adjusting perfectly to Alex's shoulders as if it was made just for him. With a growing sense of excitement, Alex opened the bag's main compartment and discovered a swirling vortex of colors. The vortex seemed to call to Alex, inviting him to step inside. Hesitating for a moment, he realized this was the magic and adventure he had always yearned for. With newfound courage, he took a deep breath and stepped through the portal, disappearing into the vibrant void. The world inside the backpack was nothing like Alex had ever imagined. It was an expansive realm filled with floating islands, luminous waterfalls, and ethereal creatures. Every corner held a new discovery, from talking animals to enchanted artifacts. Alex spent countless hours exploring this magical realm, making friends with the creatures and unraveling its mysteries. He learned the art of flying, telekinesis, and even conjuring illusions. Along his journey, he met a wise old wizard who became his mentor, teaching him valuable lessons about magic and responsibility. As Alex grew wiser and more skilled, he realized that the backpack's magic was not without its consequences. With every spell he cast, a faint shimmer of darkness would appear on the bag's surface. He soon understood that the backpack had a life of its own, and its power could be both a blessing and a curse. Determined to make the most of his time in the magical realm, Alex embarked on a quest to find the legendary \"Heart of Magic,\" an artifact said to hold the power to restore balance and harmony to the backpack's magic. Along the way, he faced challenges, encountered dangerous creatures, and forged alliances with unlikely companions. When Alex finally found the Heart of Magic, he was given the choice to either return home to his old life or remain in the magical realm forever. With a heavy heart, he made the difficult decision to say goodbye to his friends and the world he had come to love. Stepping back through the vortex in the backpack, Alex found himself back in the forest glade, the backpack now nothing more than an ordinary worn-out bag. He carried it with him, cherishing the memories of his magical journey, knowing that he had grown and learned valuable lessons about friendship, magic, and the choices that shape one's destiny. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'getting-started-with-gemini-using-vs-http.html'; this.page.identifier = '9271ed1c97_idgetting-started-with-gemini-using-vs-http'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "getting-started-with-the-vertex-ai-gemini-with-vs-http.html": {
    "href": "getting-started-with-the-vertex-ai-gemini-with-vs-http.html",
    "title": "Getting Started with the Vertex AI Gemini API with Visual Studio - Get Blogged by JoKi",
    "summary": "Getting Started with the Vertex AI Gemini API with Visual Studio Copy Markdown View Markdown GitHub Markdown Source Getting Started with the Vertex AI Gemini API with Visual Studio Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'getting-started-with-the-vertex-ai-gemini-with-vs-http'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'getting-started-with-the-vertex-ai-gemini-with-vs-http'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Getting Started with the Vertex AI Gemini API with Visual Studio') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-02-23 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Naming things in information technology is a hard nut to crack, and so it is not surprising that recent announcements about the line of Gemini products might be confusing. This article focuses on using the Vertex AI Gemini API provided by Google Cloud Platform. Generative AI on Vertex AI (also known as genAI or gen AI) gives you access to Google's large generative AI models so you can test, tune, and deploy them for use in your AI-powered applications. It's the professional or enterprise offering compared to the consumer oriented AI Studio. Access to Vertex AI Gemini API is currently offered through SDKs for various programming languages like Python, Go, Node.js, and Java. Set up Vertex AI First, you need an account on Google Cloud. If you don't already have one you can sign up and benefit from $US 300 credits to start with. To use Vertex AI it is necessary to create or use an existing project, billing needs to be enabled for that project, and the Vertex AI API needs to be enabled. Then navigate to the Artificial Intelligence section, choose Vertex AI menu and open the Multimodal entry under Vertex AI Studio. This should be your launch base to prompts and the documentation, if necessary. Gemini API offerings The Vertex AI Gemini API contains the publisher endpoints for the Gemini models developed by Google DeepMind. Gemini 1.0 Pro is designed to handle natural language tasks, multiturn text and code chat, and code generation. Gemini 1.0 Pro Vision supports multimodal prompts. You can include text, images, and video in your prompt requests and get text or code responses. Set up Visual Studio With the release of Visual Studio 2022 Version 17.5 there is a new feature available that allows for better, faster API development using .http/.rest files with an integrated HTTP client. Those files enable you to \"run\" your API endpoints and manipulate various REST calls to iterate within parameters and see the outputs in a structured way. Learn more about Visual Studio 2022 - 17.5 release announcement. Similar to the set up procedures for Getting started with Gemini using Visual Studio create a project, probably a web application or API one and create an .http file to start with. In order to keep private, sensitive information and secrets out of your source code repositories, it is recommended to use either Environment Variables, User Secrets, or Azure Key Vault to retrieve data like the project ID or an access token. Here, I'm going to create an .env file and place it into the project folder with the following content. PROJECT_ID=<your Google Cloud project ID> ACCESS_TOKEN=<your access token to Google Cloud> To access the value of a variable that is defined in a .env file, use $dotenv. More details are described under Environment Variables in the official documentation. After you logged into Google Cloud either using the Console Shell or the gcloud CLI tool (recommended) you can retrieve the access token with the following command. $ gcloud auth print-access-token Copy and paste the output as value of the ACCESS_TOKEN variable into your .env file. In case that the next request is still not authorized, make a small change to the .http fle content and try again. There seems to be a slight delay or synchronisation issue in the integrated REST client. Use the Vertex AI Gemini API The documentation on Google Cloud describes the Generative AI foundational model reference in general, and its Gemini API in particular. Here, there are two API endpoints of interest. https://{REGION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{REGION}/publishers/google/models/gemini-1.0-pro:streamGenerateContent https://{REGION}-aiplatform.googleapis.com/v1/projects/{PROJECT_ID}/locations/{REGION}/publishers/google/models/gemini-1.0-pro-vision:streamGenerateContent The difference between both is simply the selection of model - gemini-1.0-pro and gemini-1.0-pro-vision. There is the method streamGenerateContent only to generate information based on the data and prompts you send to the API. Use the Gemini 1.0 Pro model The Gemini 1.0 Pro (gemini-1.0-pro) model is tailored for natural language tasks such as classification, summarization, extraction, and writing. Generate text from text Send a text prompt to the model. The Gemini 1.0 Pro (gemini-1.0-pro) model provides a streaming response mechanism: streamGenerateContent. With this approach, you don't need to wait for the complete response; you can start processing fragments as soon as they're accessible. @projectId={{$dotenv PROJECT_ID}} @accessToken={{$dotenv ACCESS_TOKEN}} @region=us-central1 @model=gemini-1.0-pro @apiEndpoint={{region}}-aiplatform.googleapis.com # Send a text-only request POST https://{{apiEndpoint}}/v1/projects/{{projectId}}/locations/{{region}}/publishers/google/models/{{model}}:streamGenerateContent Authorization: Bearer {{accessToken}} Content-Type: application/json { \"contents\": { \"role\": \"USER\", \"parts\": { \"text\": \"Why is the sky blue?\" } } } Working with configuration parameters Every prompt you send to the model includes parameter values that control how the model generates a response. The model can generate different results for different parameter values. Learn more about model parameters. Also, you can use safety settings to adjust the likelihood of getting responses that may be considered harmful. By default, safety settings block content with medium and/or high probability of being unsafe content across all dimensions. Learn more about safety settings. @projectId={{$dotenv PROJECT_ID}} @accessToken={{$dotenv ACCESS_TOKEN}} @region=us-central1 @model=gemini-1.0-pro @apiEndpoint={{region}}-aiplatform.googleapis.com # Send a text-only request with model parameters and safety settings POST https://{{apiEndpoint}}/v1/projects/{{projectId}}/locations/{{region}}/publishers/google/models/{{model}}:streamGenerateContent Authorization: Bearer {{accessToken}} Content-Type: application/json { \"contents\": { \"role\": \"USER\", \"parts\": { \"text\": \"Why is the sky blue?\" } }, \"generation_config\": { \"temperature\": 0.2, \"top_p\": 0.1, \"top_k\": 16, \"max_output_tokens\": 2048, \"candidate_count\": 1, \"stop_sequences\": [] }, \"safety_settings\": { \"category\": \"HARM_CATEGORY_SEXUALLY_EXPLICIT\", \"threshold\": \"BLOCK_LOW_AND_ABOVE\" } } Multi-turn conversations (chat) The Gemini 1.0 Pro model supports natural multi-turn conversations and is ideal for text tasks that require back-and-forth interactions. Specify the role field only if the content represents a turn in a conversation. You can set role to one of the following values: user, model. @projectId={{$dotenv PROJECT_ID}} @accessToken={{$dotenv ACCESS_TOKEN}} @region=us-central1 @model=gemini-1.0-pro @apiEndpoint={{region}}-aiplatform.googleapis.com # Using multi-turn conversations (chat) POST https://{{apiEndpoint}}/v1/projects/{{projectId}}/locations/{{region}}/publishers/google/models/{{model}}:streamGenerateContent Authorization: Bearer {{accessToken}} Content-Type: application/json { \"contents\": [ { \"role\": \"user\", \"parts\": [ { \"text\": \"Hello\" } ] }, { \"role\": \"model\", \"parts\": [ { \"text\": \"Hello! I am glad you could both make it.\" } ] }, { \"role\": \"user\", \"parts\": [ { \"text\": \"So what is the first order of business?\" } ] } ] } The first order of business is to introduce ourselves and get to know each other. Use the Gemini 1.0 Pro Vision model The Gemini 1.0 Pro Vision (gemini-1.0-pro-vision) is a multimodal model that supports adding image and video in text or chat prompts for a text response. Note: Text-only prompts are not supported by the Gemini 1.0 Pro Vision model. Instead, use the Gemini 1.0 Pro model for text-only prompts. Generate text from a local image Specify the base64 encoding of the image or video to include inline in the prompt and the mime_type field. The supported MIME types for images include image/png and image/jpeg. @projectId={{$dotenv PROJECT_ID}} @accessToken={{$dotenv ACCESS_TOKEN}} @region=us-central1 @model=gemini-1.0-pro-vision @apiEndpoint={{region}}-aiplatform.googleapis.com # Generate text from a local image. # Command Prompt base64 conversion used: certutil -encodehex -f \"scones.jpg\" \"output.txt\" 0x40000001 1>nul POST https://{{apiEndpoint}}/v1/projects/{{projectId}}/locations/{{region}}/publishers/google/models/{{model}}:streamGenerateContent Authorization: Bearer {{accessToken}} Content-Type: application/json { \"contents\": { \"role\": \"USER\", \"parts\": [ { \"text\": \"What is this picture?\" }, { \"inline_data\": { \"mime_type\":\"image/jpeg\", \"data\": \"/9j/4AAQSkZJRgA...Wup/9k=\" } } ] } } Here's the excerpt from the streamed response. The picture shows a table with a cup of coffee, a bowl of blueberries, and several blueberry scones. There are also pink flowers on the table. Here are the steps to get the sample image and encode it in base64. The image is available here. It has been resized to a width of 512px prior to base64 encoding. https://storage.googleapis.com/generativeai-downloads/images/scones.jpg On Windows use a Command Prompt with the certutil command. > certutil -encodehex -f \"scones.jpg\" \"output.txt\" 0x40000001 1>nul Whereas on Linux or macOS use the base64 command to encode the image. $ base64 -w0 scones.jpg > output.txt Note: At the time of writing .http/.rest files in Visual Studio 2022 do not provide a functionality to read external files directly. Generate text from an image on Google Cloud Storage Specify the Cloud Storage URI of the image to include in the prompt. The bucket that stores the file must be in the same Google Cloud project that's sending the request. You must also specify the mime_type field. The supported image MIME types include image/png and image/jpeg. @projectId={{$dotenv PROJECT_ID}} @accessToken={{$dotenv ACCESS_TOKEN}} @region=us-central1 @model=gemini-1.0-pro @apiEndpoint={{region}}-aiplatform.googleapis.com # Generate text from an image on Google Cloud Storage. POST https://{{apiEndpoint}}/v1/projects/{{projectId}}/locations/{{region}}/publishers/google/models/{{model}}:streamGenerateContent Authorization: Bearer {{accessToken}} Content-Type: application/json { \"contents\": { \"role\": \"USER\", \"parts\": [ {\"text\": \"Is it a cat?\"}, {\"file_data\": { \"mime_type\": \"image/jpeg\", \"file_uri\": \"gs://cloud-samples-data/generative-ai/image/320px-Felis_catus-cat_on_snow.jpg\" }} ] } } A cat with gray and black stripes is walking in the snow. Generate text from a video file Specify the Cloud Storage URI of the video to include in the prompt. The bucket that stores the file must be in the same Google Cloud project that's sending the request. You must also specify the mime_type field. The supported MIME types for video include video/mp4. @projectId={{$dotenv PROJECT_ID}} @accessToken={{$dotenv ACCESS_TOKEN}} @region=us-central1 @model=gemini-1.0-pro @apiEndpoint={{region}}-aiplatform.googleapis.com # Generate text from a video file. # Using Google Storage as a source. POST https://{{apiEndpoint}}/v1/projects/{{projectId}}/locations/{{region}}/publishers/google/models/{{model}}:streamGenerateContent Authorization: Bearer {{accessToken}} Content-Type: application/json { \"contents\": { \"role\": \"USER\", \"parts\": [ { \"text\": \"Answer the following questions using the video only. What is the profession of the main person? What are the main features of the phone highlighted?Which city was this recorded in?Provide the answer JSON.\" }, { \"file_data\": { \"mime_type\": \"video/mp4\", \"file_uri\": \"gs://github-repo/img/gemini/multimodality_usecases_overview/pixel8.mp4\" } } ] } } Limited to Visual Studio? Absolutely not! I successfully used the same set of .http files in the following text editors and environments with the REST Client extension by Huachao Mao. Visual Studio Code - locally and online https://vscode.dev/ Google Cloud Shell Editor (Code OSS for the Web) Project IDX - https://idx.dev/ Source code: Gemini Playground I created a repository on GitHub gemini-playground that contains all samples described above with the exception of the .env file and the credentials to access the Vertex AI Gemini API. Feel free to give it a star and fork it for your own purpose. Migrate from Gemini on Google AI to Vertex AI As mentioned initially there are multiple \"Gemini AI\" offerings available. If you are new to Gemini, using the quickstarts and plans for Google AI Studio is the fastest way to get started. However, as your generative AI solutions mature, you may need a platform for building and deploying generative AI applications and solutions end to end. Google Cloud provides a comprehensive ecosystem of tools to enable developers to harness the power of generative AI, from the initial stages of app deployment to managing complex data at scale. Google Cloud's Vertex AI platform offers a suite of MLOps tools that streamline the usage, deployment, and monitoring of AI models for efficiency and reliability. Learn more about Gemini API and the Google AI versus Vertex AI differences. Lastly, why is the sky blue? Here's the response I was provided by Vertex AI Gemini API. The sky is blue due to a phenomenon called Rayleigh scattering. This scattering occurs because the shorter wavelengths of sunlight (blue and violet) are scattered more effectively by molecules in the atmosphere than longer wavelengths (red and orange). The scattered blue light is what we see when we look up at the sky, while the longer wavelengths of light pass through the atmosphere and reach our eyes as sunlight. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'getting-started-with-the-vertex-ai-gemini-with-vs-http.html'; this.page.identifier = '9271ed1c97_idgetting-started-with-the-vertex-ai-gemini-with-vs-http'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ghost-desktop-wont-start.html": {
    "href": "ghost-desktop-wont-start.html",
    "title": "Ghost Desktop on Xubuntu 17.04 won't start - Get Blogged by JoKi",
    "summary": "Ghost Desktop on Xubuntu 17.04 won't start Copy Markdown View Markdown GitHub Markdown Source Ghost Desktop on Xubuntu 17.04 won't start Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ghost-desktop-wont-start'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ghost-desktop-wont-start'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ghost Desktop on Xubuntu 17.04 won't start') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-08-08 - Filed under LinuxDevelopment (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Already before the migration from Joomla to Ghost last weekend I run the Ghost Desktop application on Windows. Now, after the successful completion it was about time to get going on my other machines. You know, the ones away from the main rig... Usually used during the evening hours, just for fun, or experimenting. Tonight, I decided to give one of my Linux systems some attention, started to upgrade some packages, and installed new software. Among those also Ghost Desktop App for Linux. On the Ghost website you get version 1.3.0 (as of writing), and it's a Debian package. Knowing that the desktop app is an Electron-based application, and I already packaged a few Electron apps myself, it would run on any Ubuntu-based system, too. Note: This post was written in Ghost Desktop running on Xubuntu 17.04 64bit Installation of Ghost Desktop Either you double-click on the downloaded .deb package and your system will prompt you to open/install the application in Software, or you can run the following command in the Terminal: $ sudo dpkg -i ~/Downloads/ghost-desktop-1.3.0-debian.deb Ghost Desktop can then be launched via the Application Menu/Launcher under ghost-desktop or if you prefer the terminal: $ Ghost The problem: Ghost Desktop won't start If you try to launch the application via the menu or any other GUI launcher you won't get any response at all. The software just isn't executed, it seems. Compared to running it in the Terminal. This might produce the following output: $ Ghost A JavaScript error occurred in the main process Uncaught Exception: Error: Unable to find a valid app at Object.<anonymous> (/usr/lib/Ghost/resources/electron.asar/browser/init.js:121:9) at Object.<anonymous> (/usr/lib/Ghost/resources/electron.asar/browser/init.js:173:3) at Module._compile (module.js:571:32) at Object.Module._extensions..js (module.js:580:10) at Module.load (module.js:488:32) at tryModuleLoad (module.js:447:12) at Function.Module._load (module.js:439:3) at Module.runMain (module.js:605:10) at run (bootstrap_node.js:424:7) at startup (bootstrap_node.js:147:9) The solution: Set permissions Fortunately, this has been reported already on GitHub by user letsjustfixit. The issue is caused by a missing permission bit on the Electron app. A temporary workaround has been documented until the package is going to be fixed. Run the following chmod to set read and execute bits on the Electron app and dependent components. Then launch Ghost Desktop again. $ sudo chmod -R +rx /usr/lib/Ghost/resources/app $ Ghost ⚡️ Welcome to Ghost \uD83D\uDC7B Happy blogging! It's great to see that such issues are handled on GitHub, and the \"fix\" is easily done. As maintainer of own Electron-based applications I'm interested in the root cause. So far, I didn't come across a similar problem (touching wood!). Thankfully, I'm going to add this to my notes on Electron. If you're familiar with this kind of problem regarding Electron packaging on Linux, give it try to fix it. On my side, I already cloned the Ghost-Desktop repository. Let's see whether I'm able to create a pull request for the Ghost community. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ghost-desktop-wont-start.html'; this.page.identifier = '9271ed1c97_idghost-desktop-wont-start'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "god-gave-rock-n-roll-to-us.html": {
    "href": "god-gave-rock-n-roll-to-us.html",
    "title": "God gave Rock 'n Roll to us. - Get Blogged by JoKi",
    "summary": "God gave Rock 'n Roll to us. Copy Markdown View Markdown GitHub Markdown Source God gave Rock 'n Roll to us. Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'god-gave-rock-n-roll-to-us'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'god-gave-rock-n-roll-to-us'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'God gave Rock 'n Roll to us.') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-26 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Okay, inzwischen sehe ich mich wirklich genötigt auch meinen Senf zum letzten Wochenende zu geben... Freitag Nach den Einträgen von Martin-shit-ich-habs-verpasst und War-ein-geiler-Rockabend-Thomas kann ich euch nur eins in Bezug auf Die Happy und Revolverheld mit auf den Weg geben. Ihr verpasst was, wenn ihr nicht auf ein Konzert der beiden Bands geht! Ich hab' ja vor einiger Zeit was zu Die Happy geschrieben und ich kann's nur noch einmal bekräftigen. Die Gruppe rockt die Hallen. Ich muss gestehen, dass ich eine tierische Vorfreude auf beide Bands hatte und bereits am Freitag Abend in der Firma schon rumgezappelt habe... Revolverheld legten schon ziemlich gut vor - ganz nach meinem Geschmack - und nach der Umbaupause für Die Happy hielt es mich auch nicht sonderlich lange in der Nähe von Thomas... Ab in die Meute und abfeiern! Ich find's immer wieder erstaunlich, dass bei der richtigen Stimmung problemlos einige Stagediver auf der rockenden Meute durch die Halle getragen werden. Richtig genial... Zwischendurch immer mal wieder lockere Sprüche von Marta, und dem Wechselspiel mit dem Publikum - Leute, das bringt richtig Laune. Quintessenz des Freitag Abend - vollständig durchgeschwitzt aber mit einem fetten Grinsen im Gesicht ging's nach Hause. Schade, dass Golo und Alex nicht - wie ursprünglich geplant - dabei waren... Samstag Gut ausgeschlafen den Tag locker mit einigen Arbeiten für die Firma und die Konferenz verbracht. Interessanter ist natürlich die Abendgestaltung - Kult ist angesagt. Wie eigentlich jeden Samstag Abend... Also, wer mich mal abends treffen möchte, kommt ins Kult. Nur irgendwie schmerzten meine Waden noch vom Vorabend, aber egal, Hauptsache es rockt... Sonntag Kult... \uD83D\uDE09 Tjoa, hat wirklich einer geglaubt, dass ich den Sonntag nicht im Kult beginne... Okay, ich bin noch dort geblieben, denn eigentlich treffe ich immer erst in den frühen Morgenstunden wieder zuhause ein. Manche würden dann ihr Frystyck machen. Nee nee, ich nehm' dann meinen Schlummertrunk und verzieh' mich in die Heija. Extrem gut gelaunt und völlig entspannt vom Musikwochenende ging's nachmittags noch auf 'ne private Fete. Auch hier lief den ganzen Abend lockere Musik - nicht ganz so rockig, aber absolut zum Geniessen. Zusammenfassend bleibt nur zu sagen, dass KISS recht haben - God gave Rock 'n Roll to us! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'god-gave-rock-n-roll-to-us.html'; this.page.identifier = '9271ed1c97_idgod-gave-rock-n-roll-to-us'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "golo--der-gartenzwerg.html": {
    "href": "golo--der-gartenzwerg.html",
    "title": "Golo, der Gartenzwerg? - Get Blogged by JoKi",
    "summary": "Golo, der Gartenzwerg? Copy Markdown View Markdown GitHub Markdown Source Golo, der Gartenzwerg? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'golo--der-gartenzwerg'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'golo--der-gartenzwerg'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Golo, der Gartenzwerg?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-22 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Da haben wir schon einen alltäglichen Namen wie Golo und dann auch noch in der Kombination. Ich find's klasse... Auch wenn ich heute extremst schlecht drauf bin. Oder um es deutlicher zu formulieren: Heute ist einer der \"Geh mir aus dem Weg, oder du heulst\" Tage... Wassup? Ganz einfach, vorhin kam von Golo selbst der folgende Hyperlink per Messenger: https://www.eltern-infothek.de/buemedcms/php/Proxy.php/de_DE/buemed/Kind/Kidstv/show,477.html Cool, endlich mal wieder was Neues im deutschen Fernsehen, aber erstens finde ich mal die Sendezeit richtig genial: 8:30 Ähhhh, hallo tickt's noch richtig? Welche Kiddies sollen denn bitteschön diese Sendung ansehen? Wisst ihr, selbst die abgespaceten Winkilalapos waren besser platziert. Aber, hey, was wundere ich mich denn wieder... Also, aktuell kenne ich Golo nur aufgrund dreier Sachverhalte: Golo \"The one and only Eisbär\" Haas, dessen literarische Erklärung / Beziehung zu Golo Mann, und nun den altergreisen Gartenzwerg... Apropos altergreis - allein dieser Umstand ist ja mal wieder ein Satire auf die Wirklichkeit in Deutschland: Kinder sind unbeliebt - that's the fact. Nicht nur, dass die jugendliche Alterstufe bis knapp zur Menopause unfähig für kinderträchtige, langfristige Beziehungen zu sein scheint, nein, auch die alterstollen Bürgersteighindernisse stehen sehr positiv dem Nachwuchs gegenüber. Kommt, ihr Kinderlein kommet. - Den Spruch gibt's vielleicht noch von der Kirche, in der aussichtlosen Hoffnung auf priesterlichen Nachwuchs, aber bei den letzten Schlagzeilen in Bezug auf Schutzbefohlenen... Gute Nacht schönes Deutschland. Nein, ich will mich nicht beschweren, ich lege lediglich die seit Jahren vertuschten Tatsachen ein wenig an die Oberfläche. Wie sieht's den in Kindergärten aus? Besonders in Großstädten? Tjoa, einen Sprachkurs in der naheliegenden Volkshochschule kann man sich auf Dauer sparen und auf diese Weise macht das Lernen, spielerisch betrachtet, wesentlich mehr Spass! Ich möchte hier nochmals betonen, dass ich Kinder mag und besonderen Respekt vor jungen Familien und Familien mit mehr als dem Normanteil an Kindern haben. Mit Pi * Daumen neun (9) Geschwistern weiß ich von was ich spreche! Aber ich schweife ab... Also, wer kennt noch irgendwelche Golos? Gartenzwerge? Hm, ob Golo einen zweiten Namen - eventuell Lars - hat? \uD83D\uDE0E Okay, wieder genug Zeugs geschrieben, ich zieh' mir nun ein wenig gepflegte Musik in die Birne und versuch' mich in der Badewanne zu lockern. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'golo--der-gartenzwerg.html'; this.page.identifier = '9271ed1c97_idgolo--der-gartenzwerg'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "goodbye-2013-welcome-2014.html": {
    "href": "goodbye-2013-welcome-2014.html",
    "title": "Goodbye 2013 - Welcome 2014 - Get Blogged by JoKi",
    "summary": "Goodbye 2013 - Welcome 2014 Copy Markdown View Markdown GitHub Markdown Source Goodbye 2013 - Welcome 2014 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'goodbye-2013-welcome-2014'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'goodbye-2013-welcome-2014'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Goodbye 2013 - Welcome 2014') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-01-02 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); This time only some personal thoughts about what happened during 2013, and what might be an outlook in 2014... Goodbye 2013 Quite frankly, 2013 turned out to be an interesting year with a number of challenges and positive activities after all. First of all, I finally managed to find enough spare time and founded the Mauritius Software Craftsmanship Community (MSCC), and within shortest time the number of members increased to more than 70 craftsmen and 200+ Likes on Facebook. Since the beginning we are organising 'Code & Coffee' meetups every Wednesday morning, and the success of our monthly meetings is increasing each time. I'm really proud of the achievement in this community. Through the MSCC and with help of the Linux User Group Mauritius (LUGM), namely Ish Sookun, it was also possible to act as a speaker - twice - at the Infotech 2013 and the Linuxfest 2013. Each time I talked and demoed about how to run Windows applications on a Linux system. There are multiple options, decisions and solutions to go for... Personally, I have to say that 2013 was way better than 2012. Working as a freelance software developer and running your own business comes with a good amount of liberty but for sure also with a big pack of responsibility and project work. It has been a great time and I was able to complete a good numbre of small to medium projects for customers all over the world. It's really interesting to work during odd hours sometimes in order to have a good conversation with a client in Australia or in the U.S. And I was able to fulfill two of my last year's resolutions... Write at least one blog article per week (on average) Achieve platinum status on Microsoft Virtual Academy (MVA) Yes, 2013 was good, and it is with pleasure to say 'Goodbye'. Welcome 2014 The list of tentative activities for this year is growing... Although, it already started back in 2013, hopefully there will be great additions during the next couple of months. Of course, I'm going to post about various issues from time to time. So, please stay tuned and I wish 'A Happy New Year' to all my readers. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'goodbye-2013-welcome-2014.html'; this.page.identifier = '9271ed1c97_idgoodbye-2013-welcome-2014'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "google-maps-api.html": {
    "href": "google-maps-api.html",
    "title": "Build anything with Google: Google Maps APIs - Get Blogged by JoKi",
    "summary": "Build anything with Google: Google Maps APIs Copy Markdown View Markdown GitHub Markdown Source Build anything with Google: Google Maps APIs Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'google-maps-api'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'google-maps-api'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Build anything with Google: Google Maps APIs') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-01-31 - Filed under DevelopmentCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Starting with a first integration of the Google Maps JavaScript API to add a map into your web site, you could add additional APIs and hence more value to your site and provide interesting features. Google Developers - Build anything with Google To get started building anything with Google tools you have to have a registered account on Google Developers. The account is compulsory to be able to enable and to manage any Google services under your responsibility. Usually, you would do this in the Google API Console. Getting started with Google Maps API Following are few links to related API provided by Google. It's a selection that has been covered during the meeting. And you might like to explore the other available options for either your effort in web development or your mobile application. Google Maps JavaScript API Google Maps Directions API Google Maps Distance Matrix API Google Maps Elevation API Google Maps Geocoding API Google Maps Geolocation API Create a Google Developer account First, you would need to sign up for a developer account with Google. You can to do this by signing up here. Eventually you should have a regular Google account already. Otherwise, you can create one for free, too. Get your API key Next, you would hit the Get a Key button to create a project on the Google API Console, activate the Maps JavaScript API and any related services, and get an API key. Like mentioned the APIs you would like to use have to be enabled in your developer account. Use Google Maps API on your website Adding Google Maps to your website is based on four steps. Create a designated <div> element with an unique identifier, ie. \"map\" Add CSS instructions to provide dimensions of the newly created div element Add a JavaScript code snippet to create the map, and Reference and load the Maps JavaScript API in a <script> element, using your own YOUR_API_KEY as URL parameter. The resulting HTML should look similar to this: <!DOCTYPE html> <html> <head> <title>Simple Map</title> <meta name=\"viewport\" content=\"initial-scale=1.0\"> <meta charset=\"utf-8\"> <style> /* Always set the map height explicitly to define the size of the div * element that contains the map. */ #map { height: 100%; } /* Optional: Makes the sample page fill the window. */ html, body { height: 100%; margin: 0; padding: 0; } </style> </head> <body> <div id=\"map\"></div> <script> var map; function initMap() { map = new google.maps.Map(document.getElementById('map'), { center: {lat: -20.258, lng: 57.475}, zoom: 10 }); } </script> <script src=\"https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&callback=initMap\" async defer></script> </body> </html> Source: Google Maps API documentation - Getting Started The above code will load a map of Mauritius, like so: Check out the numerous samples provided by Google to see what other options are available and how to create more sophisticated maps on your site. Usage limits Please bear in mind that although the Google Maps JavaScript API is free to use there are restrictions in free usage. More details are described in Google Maps JavaScript API Usage Limits. Given an average blogging site or a low frequented web site you might be just fine using the free tier provided by Google. In case that you hit the limits you can enable pay-as-you-go billing to unlock higher quotas. Meetings on Google Maps APIs A couple of weeks earlier there had been a joined meeting between the Mauritius Software Craftsmanship Community (MSCC) and the GDG Mauritius to explore the possibilities of using Google Maps API a little bit. See more information here: Google Maps APIs Build anything with Google: Google Maps APIs if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'google-maps-api.html'; this.page.identifier = '9271ed1c97_idgoogle-maps-api'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "google-maps-guestmap.html": {
    "href": "google-maps-guestmap.html",
    "title": "Visualise your visitors with Google Maps Guestmap - Get Blogged by JoKi",
    "summary": "Visualise your visitors with Google Maps Guestmap Copy Markdown View Markdown GitHub Markdown Source Visualise your visitors with Google Maps Guestmap Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'google-maps-guestmap'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'google-maps-guestmap'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Visualise your visitors with Google Maps Guestmap') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-04-04 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Thanks to an older article of Scott Hanselman I came across this gem: Google Maps Guestmap Service. The service itself allows anyone to pin their location on a world map with some basic information and icon. Looking at Scott's map with more than 1900 pins, it's truely amazing! Where are my readers located? It's quite fascinating to get to know where readers of this blog are actually coming from. Yes, of course, I already have a rough idea about your whereabouts based on Google Webmaster Tools and Google Analytics but it gives me only some blurred data. Based on my experience with the presented map I would recommend you to use Google Maps Guestmap directly in case that you would like to add yourself. Google Maps Guestmap Service is provided by MapServices It would be great to have a couple of pins soon... Thanks! Call for action: Pin yourself Actually, their service comes in two flavours: the basic, free mode and the sponsored-mode: Sponsored-mode features *No more ads shown on your map! * *No more \"donate\" button over your map! * *No restriction on number of pins shown at a given time (non-donating users can only see 500 pins a time) * *Password-protected maps (define a password that will be required to see your map) * Edit the marker-input dialog (remove fields, change the message caption) For the time being, I run the map in basic mode. But looking forward to reach the restriction of 500 pins quickly in order to donate and support them as quickly as possible. Dear readers, this is my call for action! Add your pins and push me. \uD83D\uDE09 Interestingly, the amount for a donation to MapServices is freely selectable. Please use the comment section below for your feedback and suggestions. For example, what do you think about their service? Would you use it for your website? Other purposes? And last but not least, how much money would you donate? Pin and geo-tag yourself on the Guestmap above, and watch it grow like a patchwork pattern over the next couple of weeks and months. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'google-maps-guestmap.html'; this.page.identifier = '9271ed1c97_idgoogle-maps-guestmap'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "heute-ist-ostern-und-weihnachten-gleichzeitig---.html": {
    "href": "heute-ist-ostern-und-weihnachten-gleichzeitig---.html",
    "title": "Heute ist Ostern und Weihnachten gleichzeitig... - Get Blogged by JoKi",
    "summary": "Heute ist Ostern und Weihnachten gleichzeitig... Copy Markdown View Markdown GitHub Markdown Source Heute ist Ostern und Weihnachten gleichzeitig... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'heute-ist-ostern-und-weihnachten-gleichzeitig---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'heute-ist-ostern-und-weihnachten-gleichzeitig---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Heute ist Ostern und Weihnachten gleichzeitig...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-03-24 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Halleluja!! Boah, ist mir gerade ein Stein vom Herzen gefallen... Das ist echt wie Ostern und Weihnachten zusammen. Und das am heutigen Kar-Freitag - irgendwie passend, oder? Worum geht's... Nun, seit meinem kleinen Umzug im Dezember bin ich auf der Suche nach ein wenig Quellcode gewesen. Wie? Quellcode? Ja, es handelt sich dabei um die aktuelle Entwicklungsfassung vom HTML Plugin für die Active FoxPro Pages (Version 0.7.6), die noch nicht wieder zurück ins Repository eingecheckt war... Ich hab' die letzten Monate immer wieder mal meine Rechner, Festplatten und sonstigen Backup-Datenträger durchforstet, aber just vor einigen Minuten bin ich glücklich über den Code 'gestolpert' - Man, that's insane! Total happy habe ich natürlich die Sourcen sofort in Subversion eingecheckt; das ist echt cooler als Ostern und Weihnachten zusammen. Yeah, da kann ich mich morgen endlich wieder an die Weiterentwicklung dieses Projekts setzen. Die Wünsche der Anwender sammeln sich doch langsam - Zudem habe ich selbst etliche Problemzonen und Korrekturbereiche entdeckt... Ganz zu schweigen von den neuen Ideen für weitere Features! Wenn das mal keine Frohen Ostern werden! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'heute-ist-ostern-und-weihnachten-gleichzeitig---.html'; this.page.identifier = '9271ed1c97_idheute-ist-ostern-und-weihnachten-gleichzeitig---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "high-performance-responsive-design.html": {
    "href": "high-performance-responsive-design.html",
    "title": "High Performance Responsive Design by Tom Barker - Get Blogged by JoKi",
    "summary": "High Performance Responsive Design by Tom Barker Copy Markdown View Markdown GitHub Markdown Source High Performance Responsive Design by Tom Barker Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'high-performance-responsive-design'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'high-performance-responsive-design'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'High Performance Responsive Design by Tom Barker') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-11-16 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It seems that I have a little bit of time these days. Luckily and thanks to the O'Reilly Reader Review Program I was able to get access to \"High Performance Responsive Design\" by Tom Barker. Actually, after my last review it was a tough preference compared to \"MongoDB - The Definitive Guide\" - which is hopefully now next on my reading list. Thanks to approximately 176 pages I was able to dig through this title very quickly, and honestly it was a great experience. Looking forward to see more on this topic. At the time of writing this review it wasn't available on Amazon yet: This item has not been released yet and is not eligible to be reviewed. Anyway, here's my summary which I'm going to publish on the 5th December on Amazon, too: What an eye-opener! Although, as good as everyone working in the field of web sites or web application development is talking about Responsive Web Development (RWD) Tom Barker has a simple but nevertheless shocking statement: You're doing it wrong! Okay, almost... Even though the title is explicitly addressed to web-frontend developers I would rather see it as a guidance for the web-backend developer. Yes, Tom starts his semi-scientific observations on the appearance of web sites but the actual improvement is supposed to happen on the server-side. Responsive Web Development covers way more aspects than just seemless arrangement and dynamic positioning of <div> sections and images. It's about network & bandwidth usage, battery life-time on mobile devices, load performance and rendering time, and other metrics related to surfing web sites. Thanks to Tom's experience as Director of Software Engineering and Development at Comcast, and an Adjunct Professor at Philadelphia University he seems to have a fable to bring the full story in easy to understand steps to the reader. Chapter by chapter he first addresses commonly used practices, then sheds some light on the individual aspects to finally point out what's wrong with those approaches - inclusive famous anti-patterns - and how it could be improved for better user experience on various devices. Modern web development - as advocated by others, too - should be based on mobile first experience. Start with the smallest screen, with the lowest resources and then extend the user experience gradually until reaching 4K smart TV environment. Define your development iterations on swag - Scientific Wild-Ass Guess or sometimes more politely called guesstimate - and specify your performance service-level agreement (SLA) as early as possible. Thanks to a test-driven development (TDD), use of headless web browser technology and performing continuous integration (CI) one can easily keep track of code commits and their impact on the existing code base. As a full-stack developer and recently involved in a greenfield web development project based on the MEAN-stack I am very glad that I read this book title at a very early stage of our project. Without any hesitation I'm going to recommend this book to any team member, maybe even ask them to read it as a compulsory lecture. Furthermore, I'd like to pick up this topic during our next user group meetings. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'high-performance-responsive-design.html'; this.page.identifier = '9271ed1c97_idhigh-performance-responsive-design'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "hit-refresh.html": {
    "href": "hit-refresh.html",
    "title": "Hit Refresh - Get Blogged by JoKi",
    "summary": "Hit Refresh Copy Markdown View Markdown GitHub Markdown Source Hit Refresh Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'hit-refresh'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'hit-refresh'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Hit Refresh') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-01-02 - Filed under PersonalCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Mid of October I received an interesting opportunity from our regional MVP lead Hande asking for contributions to support Satya Nadella's book 'Hit Refresh' on the MVP Award Blog. Following is an excerpt of my 'hit refresh moment' which has been published on the MVP Award blog in full here: We Love Hearing About How These MVPs Have #HitRefresh! My hit refresh moment came when I left my comfort zone completely and embarked on a journey into the unexpected. At the time I had a long-time established, safe and secured employment in a medium-sized company with fantastic colleagues and interesting projects to work on. Over the years I managed to ... When my employer offered me an CxO position in their new-to-be office in Mauritius I went all-in. Literally, my life and professional career was rewinded back to zero. Going to a foreign country not knowing anyone there, learning to communicate ... Now, looking back after more than ten years I have to admit it was the best decision ever. On private side I got married and we have two adorable children since then. In terms of professional career I have to report that I incorporated my own businesses in software development, in online content marketing, and in ... Changing my life in such a drastic way was an experience of a life-time. It turned out for the better. I have no regrets about this decision, and I would do it again any time. This post is part of the collection of #HitRefresh moments. Read more at www.hitrefreshbook.com if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'hit-refresh.html'; this.page.identifier = '9271ed1c97_idhit-refresh'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "how-did-we-run-the-virtual-developers-conference-2020.html": {
    "href": "how-did-we-run-the-virtual-developers-conference-2020.html",
    "title": "How did we run the Virtual Developers Conference 2020? - Get Blogged by JoKi",
    "summary": "How did we run the Virtual Developers Conference 2020? Copy Markdown View Markdown GitHub Markdown Source How did we run the Virtual Developers Conference 2020? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'how-did-we-run-the-virtual-developers-conference-2020'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'how-did-we-run-the-virtual-developers-conference-2020'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'How did we run the Virtual Developers Conference 2020?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-09-16 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Thankfully, we received a number of positive and encouraging comments on the recently concluded Virtual Developers Conference 2020 by the MSCC. The format of the conference being a full-time, online event was a total novum to members of our community. I mean, in the past, we already talked about live streaming some or all talks of our Developers Conference but practically nobody in the community ever did it before. And so, there was only one mission statement at the beginning. \"Create the best Report Writer in the world, out of only sawdust, bubblegum, and dreams.\" – Kerry Nietz, author of FoxTales Instead of a \"Report Writer\" it's the annual Developers Conference of the MSCC, the pinnacle event of our regular meetings. With the implications and constraints of the pandemic - full lockdown of Mauritius back in March and still ongoing - we needed to come up with a different solution. And literally baaed on those few ingredients - sawdust, bubblegum, and dreams - I did some research on the interweb to figure out how it would be possible for our community to provide the best possible approach to bring you the community experience and networking atmosphere of an offline event... without breaking the bank. Existing online meeting platforms Obviously, I looked at existing online meetings platforms first. Systems like Google Meet, Microsoft Teams, Zoom (nah, not really - AMA!), GoToMeeting or Jitsi Meet and many more offer quick and easy access to meetings online. However there are a number of technical limitations that I wanted to avoid or better said wanted to go beyond. Let me briefly list a few of them. No weird URL pattern for meeting rooms Availability of client applications across operating systems Avoidance of \"Zoom-Bombing\" Cost - free of charge was the high target Flexible layout handling Branding and customisation of each live stream / recording Low level of distraction for speakers Guided approach by track moderators etc. Although all online meeting platforms are superb for any kind of daily meetings and conference calls they run short on at least two of the bullet points mentioned above. Streaming services Ah, you might say but there are streaming service platforms, ie. Lightstream Studio, StreamYard, Twitch, Periscope, UStream, ON24, etc. Yes, indeed there are many. And surely they are an interesting option to go with. Lightstream actually looks pretty awesome, it was a recommendation from one of our community members, and it heads into the right direction with a lot of the requirements mentioned. I might give it a test run for another personal project some day. However, all of them are subscription-based and the available free tier, if any, has too many constraints that wouldn't allow us to realise what we had in our minds, e.g. limited time of live streaming. Plus, this event is a one-off so far, and it's not about signing a monthly contract for an unknown period. So, what now? Well, I had a look what others were doing... The Virtual Azure Community Day (VACD) caught my attention. Back in April they ran their first online event and documented their approach in two blog articles. First article I came across has been written by Maarten Balliauw from Belgium: Streaming a Community Event on YouTube - Sharing the Technologies and Learnings from Virtual Azure Community Day in which he nicely described the approach. Maarten refers to a second article Online meetups with OBS and Skype by Henk Boelman. And that blog post revealed everything in detail I needed to know for our conference. The combined information from both articles created the full picture though. Okay, let's dig a little bit deeper into the architecture, the pitfalls, and possible solutions. The big picture The general architecture for our approach is based on four main ingredients: GPU-enabled virtual machine (VM) in the cloud Skype with its built-in NDI technology Open Broadcaster Software, aka. OBS Studio Live streaming service, ie. YouTube Now with that information I would like to elaborate on each one a little bit more in detail. VM with GPU Stable and uninterrupted access to internet has been improved here in Mauritius and there are hardly any complaints on my side anymore. Also, recently I upgraded the outdated WiFi infrastructure here in my home office with a modern mesh system. However, live streaming is a different beast and reliable bandwidth and connectivity is key to success. Although it is possible at my place I'm not feeling comfortable to have responsibility for four live streams, and having to rely on my ISP as well as the local energy supplier which still surprises me with power outages from time to time. The solution here is to opt-in for a virtual machine operated by one of the cloud computing providers. In order to provide enough power to handle the video source(s) and streaming it has to be a GPU-enabled VM. Check your trusted cloud provider for such type of machines, mainly used for machine learning and AI activities, and look for the GPU options. You probably might be surprised about the price tags of such type of compute engine. Nothing to worry about, those prices are based on 24/7 operation over the period of a month. Yes, the prices are probably in the 4-digits range. Taking into consideration that our VM is going to run for a few hours a day only embrace the \"Pay what you use\" model and oversize your VM! Following are some links to get started: Create a free account on Microsoft Azure Quickstart: Create a Windows virtual machine in the Azure portal Get started for free on Google Cloud Platform Windows on Compute Engine and GPUs on Compute Engine I chose a Windows 10 based VM on Microsoft Azure and decided to connect via Remote Desktop Protocol (RDP) in order to provide a shared audio device to the VM. I have seen that PulseAudio could be configured in similar fashion on Linux VMs. Due to time constraints I did not explore this option. Let's use Microsoft Azure Either create a new account (recommended because of free credit allocated) or log into an existing one. I'm not going into the details of signing up here. There are enough walkthroughs on the net. On your Home screen click on Create a resource and Search the marketplace for Windows 10. Choose the Microsoft Windows 10 option, not the Preview one, and proceed. In the dropdown you can look for other editions and language packs. I usually pick the latest Pro edition. Then click on Create. During the Basics configuration there are two important choices to make - Region and Size of the VM. Your default size might be different. Click on Select size and choose an appropriate VM type and dimension from the available options of the region. Optionally, add a filter to the list of available VM sizes specifying that you want GPU support. Check for any VM size with the _Promo suffix in the name. Those offer a better price given same specs. For the Virtual Developers Conference I chose the NC6_Promo size which offers 6 vCPUs, 56 GB RAM and decent IOPS. The price tag is slightly below $500 a month. Again, don't let the price scare you. Unfortunately, a basic subscription on Azure is limited to 10 vCPUs by default and if you would like to go for more cores you need to request an increase of resources. However, six vCPUs are just fine... Here you'll find more details on the various GPU optimized virtual machine sizes. Just note the following: NC-series, NCv2-series, NCv3-series and NCT4_v3-series sizes are optimized for compute-intensive and network-intensive applications and algorithms. Enter the remaining fields and tick the checkbox at the bottom to confirm the Windows 10 license aspect. Then go back to the top of the page and click on the tab Advanced. There, click on Select an extension to install and choose the NVIDIA GPU Driver Extension on the next blade. Click on Create at the bottom of the page. Confirm the installation with a click on OK and the Azure portal takes you back to the Advanced options of the VM. The result should look like this. Finally, click on the Review + create button at the bottom and let Azure validate your entries. The resulting overview should look similar to this. And with a click on Create Azure starts the deployment of your new VM. This process takes about 4-5 minutes. Time to have break and enjoy some refreshments. Setting up the \"Studio\" Now with the VM in place it is time to work out the actual online studio and communication channels between hosts and speaker(s). That's where Skype comes into play. Skype is free of charge and available for all major operating systems. In worst case even as a web client. This provides maximum adaption on all parties involved. For the Virtual Developers Conference I can attest that we had people running on Windows, macOS and Linux - they were all welcome equally. The actors for a single track are based on the following constraints: Studio account that hosts the conference call and runs on the VM 1-2 Speaker accounts 1-2 Moderator / Host accounts This gives us a total of 5 Skype accounts bound to one live stream. You might probably ask why I create all those Skype accounts? Well, more on that in the next chapter on OBS Studio and how the integration of Skype works. It is also highly recommended that you create your Skype accounts a few days or even weeks in advance. Start using them from time to time, especially the Skype accounts for the speakers. Microsoft monitors any activities on those accounts and unusual behaviour like sign ins from different locations get noticed. Because the Skype account for speakers is recycled and used by multiple people Microsoft might consider this as an unusual activity and therefore asks for verification, especially when the login is happening in a “new” location / country. In such a situation you will be asked to verify the account. Confirm the email address and click on Next. Then kindly wait. Microsoft is going to send an email to the account’s address with the security code. It might take a few minutes until you receive the security code by email, kindly wait patiently. BTW, that's good reason to organise a dry run, especially with your speakers from abroad. Skype for Desktop on the VM There is only one real requirement in regards to Skype. And that's the choice of client application running on the VM. There you have to use Skype for Desktop because that's the only client version that provides support for the NDI® technology we are going to use to hook up OBS Studio. On your VM, open the Settings of Skype and choose the Calling options. In the Advanced settings enable Answer incoming calls automatically and Allow NDI® usage. Those two settings are the most important ones. Surely you can review other options to reduce the level of distraction and improve the visual appearance of the contact list, etc. To make it as simple as possible for the other four Skype accounts, I added them already as contacts into the designated studio account and created a conference/chat room using the name of the live stream, e.g. Batcave or Avengers Tower. The choice of Skype client for the speaker or for a host is secondary. However there are other obstacles to consider... Skype for Business You cannot use Skype for Business because those speaker / moderator accounts are not associated with a company but personal Skype accounts. Skype for Web This might be the simplest solution of all. However, the web client of Skype does not allow you to use webcam and screen sharing at the same time. Kind of weird compared to other web solutions. Skype for Desktop (macOS) During the test runs we observed that several speakers had permission issues on macOS. In order to provide the necessary permissions on macOS check your system settings. Under Privacy you need to grant permission to Skype to access your Camera, Microphone and probably Screen Recording. This shall be checked and adjusted before launching Skype as it requires to restart Skype anyways. Open Broadcaster Software, aka. OBS Studio With all Skype participants of a live stream in place it is time to configure OBS Studio. Connect to the Windows VM via RDP. Download and install OBS Studio and enable NDI® support in OBS. Download OBS Studio Install the NDI® runtime Get the NDI extension for OBS Your VM might ask for some reboots. Time to have a break and let the machine reboot. Set the stage (Scenes) That's the area where the actual customisation starts, and I'm going to provide you with some thoughts that we took into consideration. For the Virtual Developers Conference we created multiple scenes that show different elements offered by Skype - web cams and shared screen - and our visual theme consisting of MSCC branding, conference super heroes, hashtag, news ticker, rotation of partner logos, etc. Effectively each scene is a combination of Sources. More on that in the next paragraph. Add your elements to each stage (Sources) Important: Start the Skype conference call with all accounts involved before you add them to your instance of OBS Studio. Only active NDI® sources can be accessed. Most importantly is the addition of all Skype actors into your OBS scenes. Either click on the Plus button under the Sources or right-click somewhere in the list of sources to add an NDI™ Source. Then give it a name under Create new and confirm the dialog with OK. In the next dialog choose the Skype account you would like to assign to the source. It helps to have a list of the account names or Live IDs at hand to choose the right one. Complete this step with a click on OK and repeat the steps for all 4 actors in your conference call. Finally, we are going to add two specialised NDI® sources to the scene. First, we take care of screen sharing and add the Skype - Shared screen option. And then the Skype - Active speaker to round up all sources needed. The list of sources for one of our stages called Split - 3 that shows two hosts and one speaker looks similar to this. Note: The position of each source defines its z-index in the scene. The specialised source Audio (Skype active speaker) comes last to avoid any overlap with other resources. That source can even be positioned outside the viewport of the scene. The resulting scene looks like this. A scene that shows both hosts on the right-hand side and the speaker on the left-hand side. We used it at the beginning to introduce a single speaker and to run the Q&A session at the end of each talk. Keep the cameras in place After you placed and resized each NDI® source into your scene make sure that the feeds stop changing their size automatically. To achieve this right-click each NDI® source and choose Transform, then select Edit Transform, and set the Bounding Box Type to Scale to inner bounds. Confirm the transformation with Close. More details are described in the support area of Skype: What is Skype for content creators? Each Skype source provides audio! Pay attention to your settings under the Audio Mixer section. Each NDI® source that is active (visible) in the scene provides an audio stream. However each Skype source plays back everything that is being said in the conference call. Meaning, that is causing extreme echoes in the stream, and the main reason we use the specialised Skype - Active speaker source. To avoid any echoes mute all audio sources except the Skype - Active speaker which btw should be present in all your scenes. Keep an eye on the level indicator and adjust the slider of the audio level accordingly. Your peaks should never reach into the red area but stay in the lower yellow area all the time. That's the another good reason to organise a dry run before the event. Live streaming: And... Action! The final piece of the puzzle is the configuration of streaming. Henk mentions in his article to use Restream which allows you to broadcast to multiple platforms simultaneously. We thought about it but decided to use YouTube directly because we wanted to keep the audience in one place. Navigate to your YouTube Studio and configure a new video stream. If you want to run multiple streams at the same time like we did over those three days you need to schedule them ahead of time. Important: In case that it's your first time streaming be informed that YouTube has a 24 hours warm up period before you can actually use your account to stream. Other features are bound to the number of Subscribers of your channel. Plan ahead and make announcements to advertise your new broadcast channel. Now, in the Stream Settings you can retrieve the Stream key you need to paste into your encoder, for us: OBS Studio. Click on the Copy button and switch back to OBS Studio and open the Settings. Paste the key into the Stream Key input field. Confirm your settings with OK and you are ready to stream your Skype conference call in OBS Studio. Or better said: Not Yet! A studio (VM) needs an audio device By default the VM on Azure or GCP might not have an audio device for local playback. However you need to provide one for the Skype conference call to work properly. Without there is no sound on your stream. The solution here is to share your local sound card capabilities via Remote audio playback options in the Remote Desktop Connection (RDP). Check the settings of your Local Resources and choose Play on this computer. Your VM is now audio-enabled using a virtual audio driver and playing the test sound in Skype rings nicely on your local machine. To avoid any annoyance with your local audio it is recommended that you mute that RDP audio device in your mixer settings. If it doesn't show in the first place, scroll to the right. \uD83D\uDE09 Or better use an app like Ear Trumpet from the Microsoft Store for easier access and handling. Screen resolution and bandwidth That's now totally up to your hardware and screens. On my side I run a setup with four screens, among them also one with 4K resolution. However for your VM you do not need so much real estate running OBS Studio and Skype only. Adjusting the Display configuration to maximum full HD - 1920 by 1080 pixels - is more than sufficient to operate the studio. In case that your internet connectivity doesn't provide enough bandwidth, consider to lower the resolution furthere and to reduce the colour depth of your remote session. Note: OBS Studio is not 4K friendly either. No need to stress the connection after all. Community in mind... BTW, all our assets and configurations that we used for the Virtual Developers Conference are openly available on GitHub: OBS configurations and assets for MSCC events. I used that repository to provision a studio. For an emergency deployment on Wednesday night on the Kryptone track it took me roughly 10 minutes or less to have a fully operational OBS studio up and running. Finally, the article Streaming a Community Event on YouTube - Sharing the Technologies and Learnings from Virtual Azure Community Day by Maarten Balliauw has more tips and advice to often. Given that this article is now beyond the 3,500 words mark I'm going to save more of our notes for a second blog article. It will also contain more 'behind the scenes' looks of our battle stations and other helpful resources like our Guide for Speakers and Guide for Hosts. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'how-did-we-run-the-virtual-developers-conference-2020.html'; this.page.identifier = '9271ed1c97_idhow-did-we-run-the-virtual-developers-conference-2020'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "how-to-avoid-accidental-app-purchase.html": {
    "href": "how-to-avoid-accidental-app-purchase.html",
    "title": "How to avoid accidental app purchase - Get Blogged by JoKi",
    "summary": "How to avoid accidental app purchase Copy Markdown View Markdown GitHub Markdown Source How to avoid accidental app purchase Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'how-to-avoid-accidental-app-purchase'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'how-to-avoid-accidental-app-purchase'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'How to avoid accidental app purchase') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-09-02 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Handing over your Droid to one of your children might be an option to entertain them but be aware that one of the highest revenue is generated by advertisement, app and in-app purchases. Sometimes, this might be kind of surprising for you to discover that your credit card has been charged. You should keep an eye on the payment settings on your device, too. Lately, we were joking about this on some social media network, and we heard about some expensive adventures from our friends... Check your credit card statements regularly If you don't... Well, eventually you might find some \"unknown\" transactions on your credit card statement. I already had this pleasure thanks to my little engineers (read: cutie monsters). Yesterday night I went to check the balance and transactions of last month and was kind of surprised to see this: *Check your credit card statements regularly for unknown transactions. Specifically those ones prefixed by \"Google \". It might be surprising... Yup, clearly a transaction I'm 100% sure that it wasn't done by myself. Let's do a quick root cause analysis Finding the source of this issue was quite easy. I started with a search on Google using the keyword presented on the account statement: 7Druids. Skimming the results brought me directly into the Play Store, and I was presented with quite a number of apps for children and some for grown-ups, too. 7Druids GmbH has a selection of apps for children and adults. Following the breadcrumbs I found out which app might have been the root cause. Turns out someone likes to paint on Android... Well, children usually like Kids Play Puzzle Paint. The app seems to be available for free but in-app purchases not. But at least in our house-hold one question remained open: One which device did it happen? Even though the app has been installed already, you can still again on the Installed button and you will be presented with a drop-down selection of compatible devices. But most interestingly, you will also get the information on which smartphone or tablet the app has been installed already. Finding out on which device the app has been installed is visible in the drop down selection. Aha, there we go... Not my one! I guess, I have to have a word with my BWE. Anyway, let's see how we can avoid this kind of unattended in-app purchase. How to prevent accidental purchases? Actually, it's very simple to improve the situation on your device. In case that you have multiple devices running on the same Google account, you have to go through the steps on every smartphone or tablet in order to maximise protection. Go into the Settings of the Play Store app, Check the User Controls section, and Adjust the setting of Require password for purchase. By default it's configured as Never. Change it to one of the other available options, confirm the change with your Google account password and you're done. BTW, this is official procedure from the Help section of the Google Play Store: Use password protection for purchases Anything else? Yes, there are additional options you might take into consideration, like not providing any credit card at all in Google Wallet. In my case, I'm actually using a dedicated credit card which I more or less use for online expenses exclusively. Furthermore, the credit limit on that particular card is very low. In case of the inevitable event the damage won't be too high after all. Did you already have this kind of experience? Do you have any additional tipps and tricks dealing with this kind of situation? Leave a comment... Update Google sent out a mandatory email service announcement regarding improved services: Dear Google Play customer, We strive to provide you with the best experience possible across all of our products and services. We take pride in giving you the tools to use Google Play the way you want, including the ability to control how you authorize the purchases on your account. We understand some parents might have been charged for in-app purchases made by young children who did not have permission to make those purchases. As a result, we’ve added tools to help parents avoid unauthorized in-app purchases by their young children. We are also offering refunds in certain cases in line with our agreement with the FTC. Our records show that your account was previously charged for in-app purchases. If any of those charges were the result of unauthorized purchases by a minor between March 1, 2011, and November 18, 2014, and you haven’t already received a refund for those charges, you might be eligible for a refund. In order to make the refund process as easy and quick as possible, we encourage you to use the link below. To submit a refund request: Use this link to sign into your Google account and review your in-app purchase history. Select any in-app purchases that were unauthorized purchases made by a minor and click \"Refund.\" Provide the requested information for any in-app purchases selected and click \"Submit.\" Google will review your request and contact you via email about your refund status or if we have any additional questions. All refund requests must be submitted no later than December 2, 2015. If you have any questions or need further assistance with your refund request, please refer to this FAQ. You can learn more about in-app purchases and parental controls in Google Play on our Help Center. Thank you, The Google Play Team Request of refunds for unauthorised in-app purchases As written by Google the link to request for refunds will navigate you to an overview in the Play Store. Unsurprisingly, the above analysed scenario is listed on top of my potential refunds. This page can be used to request refunds for unauthorised in-app purchases in compliance with Google's agreement with the FTC. Next, you have to provide your name and contact details in case that Google has to issue a cheque if a refund to the original payment option is not possible anymore. Enter your legal name and the postal address where refund cheques should be sent in the event that we cannot credit your original form of payment. Of course, I requested a refund for this purchase... Let's see what's going to happen. I'm going to update this article accordingly. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'how-to-avoid-accidental-app-purchase.html'; this.page.identifier = '9271ed1c97_idhow-to-avoid-accidental-app-purchase'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "how-to-build-in-car-wifi-hotspot.html": {
    "href": "how-to-build-in-car-wifi-hotspot.html",
    "title": "How to build your own 'Emtel In-Car WiFi' hot-spot - Get Blogged by JoKi",
    "summary": "How to build your own 'Emtel In-Car WiFi' hot-spot Copy Markdown View Markdown GitHub Markdown Source How to build your own 'Emtel In-Car WiFi' hot-spot Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'how-to-build-in-car-wifi-hotspot'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'how-to-build-in-car-wifi-hotspot'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'How to build your own 'Emtel In-Car WiFi' hot-spot') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-12-20 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Inspired by a recent advertisement of Emtel In-Car WiFi, I thought (again) about possibilities of having my very own in-car wireless always-on internet solution. Emtel suggests that you should go to one of their nearest showrooms and buy a MIFI device... \"La nouvelle innovation Emtel est là\" Already some months back I was very pleased to read in the newspaper that Rose Hill Transportation (RHT) in corporation with Emtel started to offer wireless internet access in their buses. At that time I thought: Wow, finally someone is going to let Cyber Island happen and does not only talk about it. Unfortunately, I didn't pick up the idea thouroughly and let it slip away. Most probably because I was very busy on customer projects and organisation / administration of our new business. Why so serious? At that time, it simply wasn't important... Innovation by Emtel? Honestly? Come on, Emtel... Innovation is such a tough word and there are hardly any companies here on this island which are really innovative. I mean by definition: innovative - an adjective (of a product, idea, etc.) featuring new methods; advanced and original. And knowing about the technical background of how those MIFI devices actually work, I wouldn't even dare to use that marketing word. Since years it is possible to enable any (okay not all but almost any) decent cellular device to act as an on-demand wireless hot-spot for your WiFi devices. Even some very old Nokia telephones running on Symbian S60 have that capability. Combined with a simultaneous internet connectivity on the same device and some internal package routing we just explained (more or less) the concept of tethering. And de-mystified the magic of those MIFI devices. Eventually, someone might wonder about the legal background of this. Well, luckily Emtel themselves put an article about 'Phone as a Modem' online which clearly describes how to do it via USB or Bluetooth. My conclusion hereby is that tethering is allowed. Spontaneous WiFi access point - Why? Because we can... And because it's just damn cool! Okay, as long as you are on your own it might not be necessary to have a wireless hot-spot in your vehicle but even then there are quite a number of reasons to do it anyways. First of all, any WiFi-only mobile device is instantly able to go online. This might be your Apple iPod or iPad, your Sony PSP or PS Vita, your Nintendo 3DS, or any other Android tablet without an integrated SIM card. Second, sharing the internet connection for multiple devices reduces cost on monthly expenses. Instead of catering for multiple SIM cards and mobile web packages you would only need to have one. And last but not least, did I already mention that it is just cool? My reason to build this system is because I'd like to be able to have more comfort while I'm on the road. The children can fire up Skype and talk to their grandparents, or synchronisation services on our WiFi-only devices are still working. It's also fun to use Apple Maps to get directions here on the island. And because it's fun, cool and we can do it... In-Car WiFi - Shopping List Now, packed with our brief knowledge on how we might be able to achieve our own in-car wifi system with internet access it's time to check our list of required items. Any smartphone with built-in tethering Power charger for smartphone to use in the car (12 Volt circuit) SIM card with data access package Apps for remote administration and automated tasks When I say any smartphone with tethering support, I really mean it. Even though I personally prefer to use an Android-based smartphone you are not restricted to that I would encourage you to try the same (or at least similar) steps with a Symbian device, any Apple smartphone like the iPhone or even the iPad, or a Blackberry device. Actually, it would be awesome to have your feedback and eventually references to your installations in the comment section at the end of this article. Okay, after a quick check in my box of abandoned hardware I wasn't able to find a cellular to do the job. Knowing a couple of cool gadget guys through the Mauritius Software Craftsmanship Community (MSCC) and the UOM Computer Club I quickly posted my request on Facebook: \"Hi, I'm currently looking for an 'abandoned' (used) Android phone. Anything with USB charging will work... Brand, model, etc. doesn't matter.\" And luckily in less than one hour I closed a fair deal for a Samsung Galaxy Gio (GT-S5660). The specs for the device are okay and the Gio seemed to be a commonly used low-budget device with a good set of features. Of course, any other tethering-capable smartphone would do the job, too. An appointment was set for the next morning. Hmpf, what to do while waiting? Well, I did a little bit of research on the device and gathered some information about latest official firmware provided by Samsung and the possibilities to install aftermarket firmware / customised ROMs like CyanogenModor others onto that device. One of the positive features of the Galaxy Gio is the ability to charge the device via USB cable (even though it has a micro-USB connector). This is good for two particular reasons: First, I already have a set of various USB cables and USB power chargers which simplifies my shopping effort and second, only one standard cable necessary to hook it up on my laptop. The USB charging is also very interesting for the permanent installation in the car. Simply get an USB power charger for the cigarette lighter unit of your car and you're good to go. In my case, I already have one of those in place for my Galaxy Tab 10.1 and my BWE's Galaxy S3. If I remember correctly, I bought my power charger at the phone supply shop at Medine Mews in Port Louis but you should be able to get it anywhere on the island. Just keep an eye on the maximum allowed electric current; usually it's 1 Ampere (1A). Due to the tablet I bought a stronger one with maximum current of 2 Ampere (2A). Due to my professional activities running a software development company I already have multiple subscriptions with Emtel and it happens that one of my SIM cards for data packages is not in use. Otherwise, you might have to go and visit a showroom of Emtel (or Orange) and get one of their web packages. Quick advice: Ask for a SIM card for data package only. This might save you some bucks every month. For my installation I'm going to use an Emtel Postpaid Mobile Internet with monthly 1GB. The monthly bandwidth volume can be adjusted easily via telephone at any time. That's all for the initial setup. More about the apps later... Flashing the latest Samsung firmware As expected, the Galaxy Gio was running on an older version of Android, namely Gingerbread 2.3.3, and therefore it was just right to initiate a little flashing session. First, I tried the 'Software Update' feature of Android but Samsung didn't provide any updates - how surprising. Okay, over at SamMobile - Everything for your Samsung Mobile I got a nice list of country specific firmwares. The interesting aspect hereby is that SamMobile provides you only with firmwares that have been officially released by Samsung. All information is from Samsung KIES. Disclaimer: Remember by following the instructions here everything is on your own risk! If you brick your device, don't blame me for it! Originally, I gave the German firmware (S5660DBTKT3) a first shot but then I went for the latest one (S5660OXXKT5) available from Czech Republic. But after all, I'd say that it doesn't make a difference although I noticed that Google Books app isn't there anymore. Which is absolutely okay and saves me valuable memory space. In-Car WiFi - Manual tests Using the latest official firmware released by Samsung - Android version 2.3.6 - I was eager to check out some features and to run a couple of manual tests. First of all, I had to configure the network parameters for internet access, namely APN (Emtel: web) and connection profile. Everything went smooth and the Gio went online as expected. Then, I checked whether on-demand WiFi hot-spot and tethering was working. After a quick baptism for the ad hoc SSID name and some password arrangements I got my Tab paired successfully with the Gio and I could go online within less than a minute. Next, it was time to update a couple of pre-installed apps like Market, Talk, Voice Search, Maps, etc. to their newer counter-parts and to install some of my all-time favourite Android apps. One of them is for sure AirDroid - Best Device Manager which provides remote access to your Droid device via (wireless) network. I'm still skipping the registration part; mainly because I don't want to enable remote access via internet. As per my research on the previous day, I always came across references towards Tasker. Note: Crafty Apps EU offers a 7-days trial version of Tasker and Starter Guides on their website. You might give it a try before purchasing the paid app from the Play Store. Last but not least, I installed the Voice packages for Gingerbread's text-to-speech engine, SwiftKey Keyboard, Google Maps, and so forth. Of course, not too many apps but just some vital ones for usage in the car. Anyway, after successful installation of all apps it was time to get everything into the car and connected. The USB power charger is already in place since months and therefore I only had to swap the USB cable for the Galaxy Tab against the one for the Galaxy Gio (which is identical to the one for the Galaxy S3...) and plug in all connectors. Car's ignition system on, firing up the USB power supply and the S5660 device indicates 'Charging...' - Nicely done so far. Next, let's activate our pre-configured on-demand hot-spot and establish a data connection via Emtel. Again no problems and thanks to previous pairing and stored WiFi parameters I could surf on my tablet in less than one minute. Manual tests - Successful. My very own In-Car WiFi project is running and operational for all passengers. Pretty sleak and it didn't take that much of time or resources after all. Compared to on/off switch on the MIFI device we only have to enable or disable the Mobile AP in the Wireless settings and we are good to go. In-Car WiFi - Automation After all the compulsory steps have been completed previously it is now the time to introduce a little bit more comfort into the whole setup. Or better said: I would like to avoid as much as possible or even completely eliminate the necessity of manual interaction with the smartphone. And this is the right time to turn our attention towards any kind of Android apps that would give us the ability to perform tasks based on certain circumstances or events. As already stated, there are numbers of apps out there. Reading through various online articles, reviews and blogs it always turned out that Tasker seems to be The app for this kind of job: \"Tasker is an application for Android which performs tasks (sets of actions) based on contexts (application, time, date, location, event, gesture) in user-defined profiles or in clickable or timer home screen widgets.\" Although a couple of resources clearly state that Tasker might be too complicated for the average user, it definitely appeals towards the power-user and scripting-proof administrator. Sounds like the perfect tool for this job. Wow, even I have to say that Tasker is equivalent to a box of Lego building blocks. That app gives me a big smile and a very warm feeling... Car WiFi On Ignoring to read any kind of instructions or starter guides I was able to create, configure and run my first profile in a couple of minutes. The definition itself is very simple: Enable tethering after mobile has been switched on. Okay, let's give it a try... And to quote the IT Crowd: \"Have you tried turning it off and on again?\" Yes, now it works. Good, let's be a little bit more specific on this first profile: I only want to enable tethering after booting while I'm not at home. Sounds legit, or? Okay, there are actually various possibilities to handle this. First and most obvious, check the GPS location of the device and decide based on the coordinates whether it is necessary to activate the hot-spot or not. But this would also require that GPS location services are active (or enabled) and it takes a bit of time until the position has been located. Due to my experience while playing Ingress this can be nasty from time to time. Well, at home I have another wireless network in use and so I specify that the hot-spot should only be activated in case that the smartphone is not in the immediate vicinity of my home zone. Next, we should take into consideration that the Droid might be switched on while I'm still waiting in the yard. So far, Tasker seems to be flexible enough to handle this already based on the existing conditions. Leaving my place automatically switches the in-car WiFi on - Check! Car WiFi Off But of course, we would like to have the opposite: Switch off our hot-spot as soon as we are back. We need a new Tasker profile for this which simply disables Mobile AP as soon as we are in the proximity of the wireless network at my premises. Quick and easy, job done. Both profiles complete each other on contrary contexts and actions. Energy saving and notifications And last but least, I'm going to configure two more Tasker profiles. One that puts the mobile into Flight Mode during night hours (let's say between 20:00 hrs and 8:00 hrs), and a second one that is going to send an SMS in case that the battery status falls below 5%. That should be sufficient enough to handle most cases. If not, adjustments can be done easily. Feature Automation - Successful. By the time, I might add a couple of more profiles and gimmicks. Stay tuned... In-Car WiFi - Expenses Overall, I'm very happy about the total cost spent on this DIY project. Samsung Galaxy Gio (GT-S5660) - Rs. 1,350.- USB power charger (12V and 2A) - Rs. 500.- Emtel SIM card for Mobile Internet - Rs. 0.- (in my case, otherwise Rs. 125.-) Tasker app - $ 4.09 or ~ Rs. 120.- Couple of hours fiddling - priceless! Total amount spent: ~ Rs. 2,000.- Compared to the above mentioned Emtel advert and the cost for a Huawei MIFI E5331 of Rs. 2,799.- I not only saved some money but I have a full-featured Android smartphone including photo & video camera, phone features (call & SMS), Google Play Store and any number of Android apps. Of course, you are not obliged to install your device stationary into your vehicle. But I find it appealing and I don't have to carry another telephone with me... Note: The cost of the monthly Mobile Internet access applies in both cases and is excluded on purpose. Depending on your usage profile you might consider pre-paid or different packages. Sounds interesting? Are you going to build your own 'In-Car WiFi Hot-Spot'? Let me know about your experience... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'how-to-build-in-car-wifi-hotspot.html'; this.page.identifier = '9271ed1c97_idhow-to-build-in-car-wifi-hotspot'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "hows-mauritius-software-craftsmanship-community-going.html": {
    "href": "hows-mauritius-software-craftsmanship-community-going.html",
    "title": "How's Mauritius Software Craftsmanship Community going? - Get Blogged by JoKi",
    "summary": "How's Mauritius Software Craftsmanship Community going? Copy Markdown View Markdown GitHub Markdown Source How's Mauritius Software Craftsmanship Community going? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'hows-mauritius-software-craftsmanship-community-going'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'hows-mauritius-software-craftsmanship-community-going'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'How's Mauritius Software Craftsmanship Community going?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-11-24 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Earlier this month I received an email from Meetup.com asking about how our young community is working. Following I simply post their initial contact and the related correspondence: \"Hi Jochen Kirstätter, November 24, 2013 is your 6 month anniversary as organizer of Mauritius Software Craftsmanship Community -- way to go! We wanted to check in again to see how you're doing and if we can help out. In addition to the help you can get from Meetup HQ, you may also want to see if your members can help. An assistant organizer can help you keep your Meetup Group going and growing. Here's what Christy, organizer of Moms of Hampton Road says: \"I am so glad that I can just appoint a couple of people to take care of things, help moderate the message board, and assist in putting up Meetups. This is a big relief for me!\" [...]\" *\"Dear Meetup team, Thanks for your gently reminder about our half-year anniversary. Well, what should I say? It's been an awesome time so far. We are counting 50+ craftsmen and there are still more interested people in our community. Within less than 4 months we actually managed to be THE meetup.com community in Mauritius, with now almost double the amount of members to the next listed group. By the end of the year, I am actually looking forward to close in at 100 members. Your portal is an absolutely asset towards the organisation, announcement and management of our gatherings. Our members simply love it, and I personally take a big advantage out of the mobile apps. It is just amazing to have this kind of flexibility at hand. Recently, we started to promote the MSCC within the professional IT world of Mauritius. We already got in touch with a number of companies to see whether there are potential synergies between our vision of an technology-agnostic IT community and their daily routines. What kind of benefits the MSCC might be able to offer to their employees and in return to elaborate on possibilities they could support the community. Of course, they don't know about the MSCC and it's our 'job' at the moment to create awareness. There will be company internal circulars with a one-page overview of MSCC and we are currently working on a brief presentation, too.* [...]\" \"Hi JoKi, Thanks for your reply! We're so happy to hear you've been able to build such a strong community. Congratulations! Keep us posted on your progress! If there's anything we can do to help, please don't hesitate to reach out. [...]\" Truly, it has been an amazing journey so far, and I'm really proud of all those passionate and enthusiastic craftsmen in the Mauritius Software Craftsmanship Community that take their free spare time and come to network and socialise with other IT people here in Mauritius. Our plans for 2014 are ambitious but you have to reach for the stars to achieve great things. Thank You, craftsmen - This is your community! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'hows-mauritius-software-craftsmanship-community-going.html'; this.page.identifier = '9271ed1c97_idhows-mauritius-software-craftsmanship-community-going'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "html-plugin-0716.html": {
    "href": "html-plugin-0716.html",
    "title": "Html Plugin 0.7.16 - Get Blogged by JoKi",
    "summary": "Html Plugin 0.7.16 Copy Markdown View Markdown GitHub Markdown Source Html Plugin 0.7.16 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'html-plugin-0716'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'html-plugin-0716'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Html Plugin 0.7.16') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-21 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Am Ende der Woche reicht die Zeit doch noch für eine aktualisierte Version des HTML Plugin for Active FoxPro Pages aus. Hierbei handelt es sich um einen Bugfix-Release. Primär wurde Probleme im Zusammenspiel mit dem AfpWiki behoben. Davon betroffen sind die Methode BBCode() Links() SourceCode() Die neue Version ist wie die vorherigen unter VFP 9.0 kompiliert. Den Download gibt's hier: https://jochen.kirstaetter.name/files/html.plugin-0.7.16.exe Installation wie immer: AFP Dienst stoppen, Installation ausführen, AFP starten, fertig. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'html-plugin-0716.html'; this.page.identifier = '9271ed1c97_idhtml-plugin-0716'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "html-tag-nach-vfp-adaptiert.html": {
    "href": "html-tag-nach-vfp-adaptiert.html",
    "title": "HTML-Tag nach VFP adaptiert - Get Blogged by JoKi",
    "summary": "HTML-Tag nach VFP adaptiert Copy Markdown View Markdown GitHub Markdown Source HTML-Tag nach VFP adaptiert Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'html-tag-nach-vfp-adaptiert'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'html-tag-nach-vfp-adaptiert'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'HTML-Tag nach VFP adaptiert') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-19 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ja, es gibt auch Situationen in denen man nicht nur immer von Desktop auf Web trimmt, sondern auch mal den umgekehrten Weg einschlägt. Der folgende Beitrag nimmt den HTML-Tag fieldset als Grundlage und wir erstellen eine Shape-Klasse mit vergleichbarer Funktionalität. Das HTML-Element fieldset stellt einen Rahmen mit optionaler Überschrift dar. Mit den Controls Shape und Label (optional noch Container) können wir das ebenfalls in VFP realisieren. Hier zunächst mal der HTML-Code für ein fieldset: <fieldset id=\"Restore\"> <legend>Datenbank wiederherstellen</legend> <p>...</p> </fieldset> Das Element legend ist für die Beschriftung verantwortlich und repräsentiert unsere Labelklasse. Shape - die spartanische Variante Der erste Lösungsansatz ist sehr einfach gehalten: Wir erzeugen uns eine Shape-Klasse, die eigenständig ein zugehöriges Label instanziiert und positioniert: *====================================================================== * Fieldset-Shape * Erzeugt bei der Initialisierung ein Label zur Beschriftung des Shapes * und positioniert es. *====================================================================== Define Class FieldSet As Shape cCaption = \"\" cPrefixLabel = \"lbl\" nOffsetTop = -7 nOffsetLeft = 5 lUseLabel = .T. && Readonly @ Runtime ;-) MemberClass = \"\" MemberClassLibrary = \"\" Protected oLegend oLegend = .Null. *------------------------------------------------------------------ * Initialisierung und (optionale) Erstellung der Beschriftung. *------------------------------------------------------------------ Function Init() As Boolean DoDefault() If This.lUseLabel This.createLabel() EndIf EndFunc *------------------------------------------------------------------ * Label instanziieren und positionieren. *------------------------------------------------------------------ Protected Function createLabel() As VOID With This If Vartype(.cCaption) == \"C\" .oLegend = NewObject(Evl(.MemberClass, \"Label\"), .MemberClassLibrary) If Vartype(.oLegend) == \"O\" With .oLegend As Label .Name = This.cPrefixLabel + This.Name .BackColor = Thisform.BackColor .BackStyle = 1 .Anchor = 0 .Left = This.Left + This.nOffsetLeft .Top = This.Top + This.nOffsetTop .Anchor = Bitclear(Bitclear(This.Anchor, 2), 3) .ZOrder(0) EndWith This.cCaption = This.cCaption EndIf EndIf EndWith EndFunc *------------------------------------------------------------------ * Nachträgliche Änderungen der Caption gestatten. * Dies ermöglicht auch das Aus-/Einblenden des Controls. *------------------------------------------------------------------ Function cCaption_Assign(tcValue As String) With This If Vartype(.oLegend) == \"O\" .oLegend.Caption = m.tcValue .oLegend.Visible = Not Empty(m.tcValue) EndIf .cCaption = m.tcValue EndWith EndFunc *------------------------------------------------------------------ * Schreibschutz für This.lUseLabel implementieren. *------------------------------------------------------------------ Function lUseLabel_Assign(tlValue As Boolean) Error 1740, \"lUseLabel\" EndFunc *------------------------------------------------------------------ * Objektreferenz sauber auflösen und Label entsorgen. *------------------------------------------------------------------ Function Destroy() As Boolean With This If Vartype(.oLegend) == \"O\" .oLegend = .Null. EndIf EndWith EndFunc EndDefine Der gezeigte VFP-Quellcode dürfte weitestgehend selbsterklärend sein. Wenn in der Designzeit dem Shape die Caption gestattet wird, so wird zur Laufzeit ein Labelobjekt aus der angegebenen Klasse erzeugt und als Referenz gespeichert. Das Label erhält den gleichen Namen wie das Shape jedoch mit frei wählbarem Prefix, da wir eindeutige Objektnamen benötigen. Sofern bei der Initialisierung noch keine Beschriftung angegeben sein sollte, wird das Label noch nicht angezeigt. Die Caption kann zu jedem beliebigen späteren Zeitpunkt gesetzt (und damit eingeblendet) und wieder ausgeblendet werden. Zur Vermeidung von hängenden Referenzen und daraus resultierenden C0005-Fehlern (samt Komplettabsturz) tragen wir dafür Sorge, dass das Label ebenfalls sauber bei der Zerstörung des Shapes entfernt wird. Wichtig ist hierbei ebenfalls, dass wir die Eigenschaft lUseLabel zur Laufzeit mit einem Schreibschutz versehen. Schliesslich soll der Entwickler sich bereits in der Entwicklungsumgebung entscheiden, ob eine Beschriftung erforderlich sein könnte oder nicht. Bis denne, JoKi PS: Die fehlenden Assign-Methoden für weitere Eigenschaften sind Hausaufgabe... \uD83D\uDE01 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'html-tag-nach-vfp-adaptiert.html'; this.page.identifier = '9271ed1c97_idhtml-tag-nach-vfp-adaptiert'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "htmlplugin.html": {
    "href": "htmlplugin.html",
    "title": "HTML Plugin for Active FoxPro Pages - Get Blogged by JoKi",
    "summary": "HTML Plugin for Active FoxPro Pages Copy Markdown View Markdown GitHub Markdown Source HTML Plugin for Active FoxPro Pages Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'htmlplugin'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'htmlplugin'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'HTML Plugin for Active FoxPro Pages') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2009-12-14 - Filed under Projects (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); HTML Plugin for Active FoxPro Pages Motivation Html Plugin is primarily a plug-in for Active FoxPro Pages. But that's only half the truth. The plug-in itself is build as a component and offers stand-alone functionality for VFP developers and through COM as well for other programming languages. Setup and Installation The setup routine installs and registers the component in Windows. The current version of the setup is available here: https://jochen.kirstaetter.name/files/html.plugin-0.7.16.exe Just get the file and follow the installation instructions. Usage in Visual FoxPro In VFP you just create an object like so: Set Procedure To \"html.plugin.dll\" Additive Html = CreateObject(\"CHTML\") If you have to use COM instead, then this works just fine: Html = CreateObject(\"AFPPlugin.Html\") It was quite funny to get this component working in .NET Framework and I'm still working on this topic, but at the moment I got it to create my own namespace (tree) based on this single DLL. After you added a reference to the COM server, you'll have an new namespace tree instead of just one plain entry: ProLib.AFP3.Plugin.Html and due to COM interoperability in .NET the instance name of the class is simple HtmlClass. I wrote a sample solution that's able to convert an RSS Feed to a .NET DataSet through the Html Plugin. Well, not too hard to implement in pure .NET but the fact is, that the plug-in provides the same functionality to VFP developers. And Html Plugin is a core part of AfpWiki - a Wiki engine on top of Active FoxPro Pages. Actually the Html Plugin does all the stuff related to Regular Expressions. It's really nice to see that just one line of code parses all hyperlinks of the entire document and converts them into clickable links with 'title' attribute and protocol indicator. There is still a lot of work to do inside of AfpWiki, like backup and restore of the database, online upgrade mechanism as well as an abstraction layer to user authentication and management, but this will be implemented during a couple of weeks. You might check out https://faq.dfpug.de to see the system in action. Ah, before I forget to mention Html Plugin and AfpWiki are both available for free (as in beer g). So, if anyone of you is interested in a Wiki solution just drop me a note. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'htmlplugin.html'; this.page.identifier = '9271ed1c97_idhtmlplugin'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "http-404.html": {
    "href": "http-404.html",
    "title": "HTTP 404 - Get Blogged by JoKi",
    "summary": "HTTP 404 Copy Markdown View Markdown GitHub Markdown Source HTTP 404 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'http-404'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'http-404'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'HTTP 404') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-13 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Spass muss sein... Ich weiss nicht mehr, wann ich den benutzerdefinierten HTTP 404 ErrorCode eingebaut habe, aber heute Nachmittag holte er mich wieder ein... Worum geht's eigentlich? Well, bei Nicht-Existenz von URLs besteht die Möglichkeit im Webserver sogenannte Antwortseiten für bestimmte HTTP-Codes zu deklarieren. Die dreistelligen Nummern sind dabei klassizifiert: 2xx - OK-Meldungen 3xx - Modify-Meldungen 4xx - Clientseitige Fehlermeldungen 5xx - Serverseitige Fehlermeldungen Der HTTP 404 sagt aus, dass die gewünschte URL nicht auffindbar ist. Im Normalfall gibt's eine langweilige Standardantwortseite des Browsers bzw. des Servers. Aber nicht so in der AFP FAQ... Wie ich schon schrieb, hatte ich vor Monaten das Problem, dass es eben nicht langweilig sein soll und habe mir ein paar Gedanken über 404er Messages gemacht. Man glaubt gar nicht, welch' Schätze in den Weiten des Internets auffindbar sind. Nun, für die FAQ habe ich mir dann auch was zusammengeschrieben bzw. entliehen: https://www.afpfaq.de/?f=170 Da es keinen Artikel in der FAQ mit der Referenznummer 170 (wie auch 171) gibt, kommt eben ein HTTP 404 mit seiner Message. Achja, es sind sprachbedingt unterschiedliche Aussagen... Have fun. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'http-404.html'; this.page.identifier = '9271ed1c97_idhttp-404'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "i-am-iron-man.html": {
    "href": "i-am-iron-man.html",
    "title": "I am Iron Man! - Get Blogged by JoKi",
    "summary": "I am Iron Man! Copy Markdown View Markdown GitHub Markdown Source I am Iron Man! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'i-am-iron-man'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'i-am-iron-man'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'I am Iron Man!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-08-13 - Filed under PersonalActivity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Living near the sea side offers a number of fun and leisure activities on, in, and under the water. Thanks to my BWE I was given a generous gift voucher for my birthday back in May. Due to our work, private constraints and the children being at school, it's only now that we finally managed to get our stuff together. Fly what? The voucher is for a session using a Flyboard. More precisely it's about an activity called \"Fly like Iron Man n’ Dive like a Dolphin\" provided by FLYn'Dive in Grand Baie. First thoughts that came across my mind, either it will be like Marty McFly on his Hoverboard in Back to the Future or really going to have this remote idea of being Tony Stark in one of his Iron Man suits. Driving up to the North was pretty smooth and after a decent breakfast at La Croisette it was about time to meet the crew. We met on the public beach near Sunset Boulevard and despite a little hiccup in the booking, (nah, not really important), my lovely wife and I finally ventured out on the boat. Check it out... A quick briefing, and I was already floating in the warm waters directly out of the bay. The boots / the board strapped on tightly. Actually, it's kind of similar to a snowboard... just without the cold and the snow. Enough writing. Why not watch the video instead? It takes a bit of strength in your body core to keep the balance on the board and therefore the jets of water. It feels a bit like standing in an air-inflated bouncing castle or big trampoline. Only difference, the ground is 'moving' and pressure comes from the bottom. Weird situation. What more to say? What an awesome birthday present! Such great fun in the water and in the air. I'm looking forward to do it again! Practice makes perfect, as they say, and later this year we're going to have family and friends coming down to Mauritius. There'll be an opportunity... Cheers, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'i-am-iron-man.html'; this.page.identifier = '9271ed1c97_idi-am-iron-man'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ich-will-feuerwehrmann-werden.html": {
    "href": "ich-will-feuerwehrmann-werden.html",
    "title": "Ich will Feuerwehrmann werden - Get Blogged by JoKi",
    "summary": "Ich will Feuerwehrmann werden Copy Markdown View Markdown GitHub Markdown Source Ich will Feuerwehrmann werden Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ich-will-feuerwehrmann-werden'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ich-will-feuerwehrmann-werden'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ich will Feuerwehrmann werden') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-08 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Dieser Satz dürfte sicherlich noch einigen von uns bekannt sein. Wobei sich das sicherlich in Alterbereiche splitten lässt. Die einen zu alt, die nächsten im optimalen Alter und weitere wiederum zu jung für dieses Zitat Wer von euch kann sich noch an Grisu den Drachen erinnern? Ich fand und finde die TV-Serie richtig genial. Vielleicht sind nur gegenwärtig die Geschmäcker ein wenig anders. Dennoch empfinde ich diese Art an Sendungen noch lehrreich - sozusagen mit einer offensichtlichen Message. Aber ich möchte nicht ablenken, sondern auf die Ursache für diesen Blogeintrag zurückkommen. Heute Abend durfte ich - wie inzwischen seit grübel mind. 5 Jahren - wieder einmal 2 Schulstunden Unterricht für angehende Feuerwehrfrauen und -männer geniessen. Ja, auf Grund meiner langjährigen Aktivität als Ausbilder in der Gefahrstoff-Einheit der Wehr Rockenhausen bietet es sich ja schon fast zwangsläufig an, dass die beiden Unterrichtseinheiten zu \"Gefahrstoffen und gefährlichen Gütern\" entsprechend umgesetzt werden. So auch diesen Abend... Ich find's immer wieder erstaunlich und positiv wie heterogen die Verteilung in dem Grundlehrgang zum Feuerwehrmann sind. Zum einen gibt's einen wirklich hohen Anteil an Mädels und Frauen - waren locker wieder 40+% dabei und als weiteren Aspekt die Altersverteilung... Ich find's wirklich cool, und danke den Verantwortlichen Lehrgangsgestaltern, dass ich die beiden Einheiten lehren darf. Nach dem Einstieg in atomare Stoffe und Radioaktivität sowie den damit verbundenen Gefahrenpotentialen, Schutz- und Abwehrmaßnahmen ging's nach einer kurzen Pause über in den Bereich der chemischen Gefahrstoffe. Es ist immer wieder spassig, wenn man die einzelnen Ziffern der Gefahrnummer und deren Bedeutung durchgeht. Ich hoffe, dass ich dabei weder den Ernst des Stoffes noch den Spass am Feuerwehrdienst aus der Wanne kippe, aber meiner bescheidenen Meinung nach, sollten ernste Themen locker-flockig vermittelt werden, und gerade Radioaktivität sowie Chemie strotzen ja vor Begeisterung... \"Also, wenn ihr mal die Chance bekommt in einen CSA zu kommen, nutzt es! Es macht wirklich Spass.\" - Man glaubt gar nicht, wie verwirrend so eine Aussage in Kombination mit einer projezierten Folie für die Anwärter sein kann... Hehe, es ist so... Alright, genug getippert. Die zwei Einheiten sind natürlich viel zu schnell vorüber gegangen. Ich hab' auf alle Fälle wieder sehr viel Spass gehabt und bin auch der Annahme, dass sowohl die Anforderungen des Fragenkatalogs erfüllt, die Erwartungen des Lehrgangsleiters realisiert und hoffentlich das Interesse bei den AnwärterInnen geweckt wurde. Ich freue mich auf den nächsten Lehrgang. Vielleicht bist du dabei... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ich-will-feuerwehrmann-werden.html'; this.page.identifier = '9271ed1c97_idich-will-feuerwehrmann-werden'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "icomm17.html": {
    "href": "icomm17.html",
    "title": "InterCommunity 2017 - Get Blogged by JoKi",
    "summary": "InterCommunity 2017 Copy Markdown View Markdown GitHub Markdown Source InterCommunity 2017 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'icomm17'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'icomm17'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'InterCommunity 2017') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-10-12 - Filed under CommunityActivity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Following an informative posting by Duskh Koonjoobeeharry, founder of the Atlassian User Group Mauritius on Facebook, I signed up first time to attend the annual InterCommunity 2017 by the Internet Society (ISOC). This year marked the 25th anniversairy of the Internet Society, and a 24 hours live stream among several nodes across the globe gave me an opportunity to learn more about the association. Mauritius actively participated as one of the 16 Interactive Regional Nodes world-wide. Having Flic En Flac listed among other big cities like Los Angeles, Singapore, Geneva, or Montevideo indicates that it's not just about location or size but active participation and contribution. The interactive node here in Mauritius was possible thanks to AFRINIC who generously sponsored the venue and handled all the logistics. What to expect? Although I already knew a little about the existence and work of the Internet Society (in Mauritius) based on a previous invitation to attend the AFRINIC-25 conference, I was mainly interested in the advertised panel discussion dubbed \"Digital Divides and the Internet Economy: The case of Mauritius\". It would have been great to get information on the past, current and hopefully plans of the future. Attending the event would also give me a chance to meet new people, and learn from their experience and knowledge. Literally next door... AFRINIC chose the Sofitel Hotel in Wolmar for their get together. So, literally for me it was just next door, and although leaving just-in-time I was among the first to show up at the venue. Later on, it was reported that some attendees got stuck in traffic jam coming down the link road from Mon Desir towards Beaux Songes. Yes, that's a daily obstacle. Luckily, my office is located in Quatre Bornes and therefore I'm going opposite direction the other days. The registration went smooth given nobody was around, and while having a cup of tea for breakfast Kevin G. Chege, ISOC, joined me and we had a brief conversation about my expectation of today's event, the Internet Society in general, and the IT communities in Mauritius. Great, exactly what I was looking for. \uD83D\uDE09 Before the sessions kicked off I managed to chat a bit with Logan and Yashvi about the upcoming Code Wars hackathon organised by the Computer Club of the UoM. Fantastic idea and yes it's important to give students a chance to get their \"hands dirty\" and to participate in coding writing activities. Later on, SM sat next to me and we had some exchange about InterCommunity 2017, the Internet Society as well as AFRINIC. Finally, some folks of LSL Digital came through the traffic jam, and started their own live stream on Facebook. Panel discussion and livestream According to the schedule the local panel discussion was set for one hour starting at 10:00 hrs (6:00 hrs UTC). Vymala Thuron from AFRINIC managed the handover from the broadcasting HQ in Brussels and kicked off the conversation with some interesting questions for the four panelists. Following I'm trying to summarise what was discussed. Bandwidth Mr Johnny Lim Fook, IP & Broadband Manager of Mauritius Telecom mentioned that with the development of fiber networks and the constant increase of bandwidth the demands of companies but also households have gone up, too. The new challenges are about providing a reliable network infrastructure for permanent eLearning and more remote work scenarios. The aim would be to create local nodes for providers of Content Distribution Networks (CDNs) and to provide higher capacity on data caches in Mauritius. Working together for the better of all Given the existing technology and infrastructure it is now more an issue between people to agree on corporation rather than keeping the competition alive, says Mr Patrick Rene. He highlighted that sharing is important in order to move Mauritius to the next level of a knowledge-based society. Pricing in Mauritius Despite the improvements of technology and the easier access to internet connectivity one major obstacle still remains according to Mr Alan Barrett, CEO of AFRINIC, which is pricing. Whether it is the current price structure of last mile lines or hosting space in local data centres. The cost is still too high in Mauritius. Handling of cyber security With the rapid adoption of Internet of Things (IoT) Mr Kaleem Usmani, Assistant Manager CERT-MU of the National Computer Board (NCB) explained that it is important that the Mauritian government is aware of recent threats and that education of the general public in regards to cyber security needs more attention. Free WiFi given by various local providers is surely interesting to nourish easy access to the internet but it is not without risks. There were more interesting questions on the digital divide but overall it can be said that Mauritius has a fair standing in Africa. Watch the full session on YouTube or here: Prior to the hand-over to the next InterComm node in Johannesburg all attendees were kindly asked to join the birthday cake ceremony here in Mauritius. Exhaustion of IPv4 addresses After a quick break and some refreshments Mrs Madhvi Gokool of AFRINIC gave an overview of the IPv4 address management and it's exhaustion in the near future. Several Regional Internet Registries (RIRs) are already out of addresses and the few remaining ones introduced rigid regulation on the application and assignment of addresses. It was also mentioned that the adaption of IPv6 is increasing and that current internet service providers (ISPs) should give some more attention to dual-stacking network hardware instead of masquerading their clients into private IPv4 address spaces. Thanks to the constant climb of IoT devices the necessity of IPv6 is unavoidable. Share more local content Right now Mauritius is still consuming more content than actually creating and sharing information. Whether it is through video services like Netflix, Hulu or Amazon Prime several attendees showed their concerns that our island could be cut-off from the interweb very easily. The recent incident on one of the internet sea cables also made this very prominent. On the one hand it would be positive to gain more resilience and redundancy on internet connectivity and on the other hand it is welcoming to offer more content and information produced locally. As stated, with better understanding and corporation between local service providers and a revised price structure this could be achieved soon, given the existing technology available today. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'icomm17.html'; this.page.identifier = '9271ed1c97_idicomm17'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "iis-7-ein-web-server-der-next-generation.html": {
    "href": "iis-7-ein-web-server-der-next-generation.html",
    "title": "\"IIS 7 - Ein Web-Server der 'Next Generation'?\" - Get Blogged by JoKi",
    "summary": "\"IIS 7 - Ein Web-Server der 'Next Generation'?\" Copy Markdown View Markdown GitHub Markdown Source \"IIS 7 - Ein Web-Server der 'Next Generation'?\" Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'iis-7-ein-web-server-der-next-generation'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'iis-7-ein-web-server-der-next-generation'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '\"IIS 7 - Ein Web-Server der 'Next Generation'?\"') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-27 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es wird interessant werden... *Der Stratege denkt an Web 2.0, der Entwickler vielleicht an AJAX, Webservices, etc. Schön wäre dabei natürlich auch einen Web-Server zu haben, welcher mit der Entwicklung Schritt halten kann. Ist IIS 7 die richtige Antwort für Sie? Bilden Sie sich Ihre eigene Meinung.* Anflug Am Dienstag Abend ging's mal wieder auf Achse. Dieses Mal nach Stuttgart auf einen Microsoft TechTalk zum IIS 7.0. Stuggi, klar, dass wir auch einen kleinen Schwenk über Wendlingen machen und den Tiger abholen. Schliesslich gilt es wieder als 'DreamTeam' vertreten zu sein. Zusammen mit Willi liefen wir dann wetter- und verkehrsbedingt ein wenig verspätet im Novotel in Stuttgart Stammheim und genossen den informativen Beitrag von Bernhard Frank. IIS 7 ist neu Klar, das ergibt sich ja bereits implizit, da Windows Vista bzw. Longhorn Server aktuell in der Betaphase sind. Dennoch sind die Umbaumaßnahmen im IIS und die Modularisierung wirklich interessant gelöst worden. Aus den bisher unabhängigen Eventhandlings im Webserver und der ASP.NET Umgebung wurde eins. Weiterhin ist das Konzept für ISAPI Filter und ISAPI Extensions vollständig dem Refactoring zu Gute gefallen. Die Details und massigen Featurelisten werden sicherlich auf den offiziellen Produktseiten zu finden sein, ich spare mir u.a. aus Platzgründen die Wiederholung \uD83D\uDE42 ISAPI und andere Schnittstellen Interessant ist auf alle Fälle die Option, dass weiterhin bestehende ISAPI Extensions wie beispielsweise Active FoxPro Pages direkt nutzbar sein werden, und dass weitere Schnittstellen für die Entwicklung von Server-Addons bereitstehen. Dies wurde auch eindrucksvoll anhand von verschiedenen ISAPI Filter per IHttpModule Schnittstelle gezeigt. Für die Implementierung von ISAPI Extension empfiehlt sich die Verwendung der IHttpHandler-Klasse. Was übrigens in keinster Weise auf den IIS 7 speziell ausgelegt ist. Man kann bereits seit einiger Zeit im .NET Framework eigene HttpModule und HttpHandler schreiben und im IIS 5.x bzw. IIS 6 nutzen. Das Neue dabei ist, dass dies nun global und applikationsspezifisch konfiguriert und genutzt werden kann. Achja, neben den .NET Interfaces IHttpModule und IHttpHandler gibt's die nahezu identische API für native Programmierung, etwa C oder C++, in Form von CHttpModule und CHttpHandler. Monolithische Konfiguration Neben den sehr hohen Ähnlichkeiten zu den Dynamic Shared Objects (DSO) des Apache kamen wir dennoch in der Pause zu einem Punkt, der weiterhin im IIS nach meinem Verständnis unzureichend gelöst ist und wurde. Die XML-Konfiguration für den Webserver bzw. die Website ist weiterhin monolithisch und gestattet es nicht, dass man die Konfiguration über mehrere Dateien splitten und nutzen kann. Das ist wirklich schade. Sicher taucht nun die Frage auf, wozu man das brauchen sollte... Nun, ganz einfach. Wir nehmen mal eine ASP.NET Anwendung eines Drittanbieters, exemplarisch DotNetNuke oder eben sonstwas. Die gängigen Einstellungen werden in der web.config hinterlegt und genutzt. Vielleicht eben auch individuelle Vorlieben und so. Im Falle eines Upgrade der Software mit Erweiterungen und/oder Korrekturen an der grundsätzlichen Konfiguration laufen wir in ein Problem hinein. Zwar nur ein leichtes, aber auf Dauer und steigende Häufigkeit wird es sicherlich zur Nervensäge. Hier bietet der Apache mit der Include-Direktive ganz erhebliche Vorteile. Tolle Demos und runde Sache Insgesamt bleibt bei mir der Eindruck zurück, dass das IIS-Developerteam weiter ihre Hausaufgaben gemacht haben und dem Riesenmonster IIS in der siebten Version sehr stark die Federn gestutzt haben. Bereits mit dem IIS 6 und den verschärften Sicherheitseinstellungen stieg die Gunst, aber mit der neuen Modularisierung, der Entrümpelung der Architektur und den vereinfachten Programmierschnittstellen wird der Microsoft Webserver interessanter. Active FoxPro Pages Ehrlich gesagt, bin ich persönlich noch nicht dazu gekommen AFP im IIS 7 zu testen. Dennoch sind die bisherigen Aussagen von Microsofties und IIS-Experten eintönig, dass bestehende ISAPIs problemlos funktionieren werden. Beruhigend zu wissen. Dennoch werde ich eventuell die nächste Zeit mal für einen ausführlicheren Test unter Windows Vista Beta nutzen und berichten. Und wie ich schon sagte, die IHttpHandler-Klasse kann man schon länger nutzen... Hier wird es sicherlich auch noch Neuigkeiten geben. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'iis-7-ein-web-server-der-next-generation.html'; this.page.identifier = '9271ed1c97_idiis-7-ein-web-server-der-next-generation'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "in-80-zeilen-um-die-vfp-tabelle.html": {
    "href": "in-80-zeilen-um-die-vfp-tabelle.html",
    "title": "In 80 Zeilen um die VFP-Tabelle - Get Blogged by JoKi",
    "summary": "In 80 Zeilen um die VFP-Tabelle Copy Markdown View Markdown GitHub Markdown Source In 80 Zeilen um die VFP-Tabelle Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'in-80-zeilen-um-die-vfp-tabelle'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'in-80-zeilen-um-die-vfp-tabelle'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'In 80 Zeilen um die VFP-Tabelle') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-29 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ups, I did it again... Oder so ähnlich, aber da ich gestern / heute ein wenig durch meine Kollegen zu Zwangsarbeit genötigt wurde, dachte ich mir die Zeit des Wartens könnte man anderweitig nutzen. Nun, in Vorbereitung für den kommenden Webcast zu Visual FoxPro und Visual Studio 2005 brauche ich ein wenig Übung und ein, zwei Beispiele wie man von C# auf die Datenbankfähigkeiten von Visual FoxPro zugreift. Well, gedacht, gemacht... Und so habe ich mir dann einen wirklich simplen VFP-Befehl als Vorlage genommen: Browse Mit dem Browse-Befehl kann man den Inhalt einer Tabelle in einem Grid anzeigen lassen und manipulieren. Sofern keine Tabelle im aktuellen Arbeitsbereich geöffnet ist, bietet Visual FoxPro den Datei-Auswahldialog für DBFs an. Für einen VFP-Entwickler gehört dieser Befehl zum Standardrüstzeug. Egal, ob auf lokal oder remote liegende VFP-Tabellen oder ResultSets aus ODBC-Connections (Views und Cursor) zugegriffen wird. Und damit wären wir auch bei einer sinnvollen Aufgabe für meine weiteren Schritte im Reich des .NET Frameworks unter der Herrscher des C#. Wir schreiben eine Windows-Anwendung zum Öffnen, Anzeigen und Manipulieren von Visual FoxPro Tabellen. Klingt spassig, oder? Spass hat es auf Fälle gemacht. Also, Visual Studio angeworfen, neues C# Windows-Anwendung Projekt namens VfpBrowse erstellt. Die automatisch erstellte Form-Klasse polieren wir ein wenig auf und verpassen ihr noch ein paar Felder, die wir im weiteren Verlauf brauchen werden: public class frmVfpBrowse : System.Windows.Forms.Form { private string path; private string file; private OleDbDataAdapter da; private DataSet ds; //... } Jetzt kümmern wir uns ein wenig um die optische Ausstattung und werfen ein paar Controls auf die leere Form: Label Textbox Button für den Auswahldialog FileDialog-Komponente Button für Browse Button für Update und zu guter Letzt ein DataGrid Das Ganze hübsch angeordnet und per Anchor-Eigenschaft festgetackert. So, jetzt kümmern wir uns um die Ereignisse, die Auftreten können. Da hätten wir zuerst den Click für die Dateiauswahl; diesem geben wir die FileDialog-Komponente für die Selektion der anzuzeigenden VFP-Tabelle. private void cmdSelect_Click(object sender, System.EventArgs e) { try { openFileDialog1.Filter = \"Visual FoxPro tables (*.dbf)|*.dbf\"; openFileDialog1.FilterIndex = 2; openFileDialog1.RestoreDirectory = true; } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } if(openFileDialog1.ShowDialog() == DialogResult.OK) { txtTable.Text = openFileDialog1.FileName; } } Als nächstes das Click-Ereignis des Browse-Button; hier nutzen wir die Fähigkeiten der OleDb-Klassen des .NET Framework. Mittels OleDbConnection öffnen wir die VFP-Tabelle, selektieren alles komplett in einen DataAdapter und füllen ein ungebundenes DataSet, welches dem DataGrid als Datenquelle zum Futtern zugeworfen wird. Fein, fein... damit haben wir die Anzeige der Tabelle. private void cmdBrowse_Click(object sender, System.EventArgs e) { try { this.path = Path.GetDirectoryName(txtTable.Text); this.file = Path.GetFileNameWithoutExtension(txtTable.Text); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } string ConnString = \"Provider=vfpoledb.1;Data Source=\" + this.path + \";Persist Security Info=false;\"; OleDbConnection Conn = new OleDbConnection(ConnString); try { Conn.Open(); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } try { this.da = new OleDbDataAdapter(\"SELECT * FROM \" + this.file, Conn); OleDbCommandBuilder cb = new OleDbCommandBuilder(this.da); da.InsertCommand = cb.GetInsertCommand(); da.UpdateCommand = cb.GetUpdateCommand(); da.DeleteCommand = cb.GetDeleteCommand(); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } this.ds = new DataSet(); this.ds.Clear(); try { this.da.Fill(this.ds); grdTable.DataSource = this.ds.Tables[0]; } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } } Als nächstes spendieren wir dem Update-Button die Fähigkeit per DataSet der Änderungen und dem bekannten DataAdapter die geänderten Informationen zurück in die VFP-Tabelle zu schreiben. Achja, dem DataAdapter haben wir natürlich im Vorfeld bereits von einem OleDbCommandBuilder erklären lassen, wie sowas zu funktionieren hat. private void cmdUpdate_Click(object sender, System.EventArgs e) { if(this.ds.HasChanges()) { DataSet dc = this.ds.GetChanges(); try { this.da.Update(dc); this.ds.AcceptChanges(); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } } } Cool, oder? Nunja, das ist erst die halbe Miete, denn die richtige Arbeitserleichterung unter Visual FoxPro besteht darin, dass die Feldinhalte direkt geändert und bei einem Zeilenwechsel ad hoc zurückgeschrieben werden. Hehe, hier für habe ich mir einen Trick einfallen lassen: Das entsprechende Ereignis des DataGrid wird an die gleiche Methode wie der Update-Button gebunden... this.grdTable.CurrentCellChanged += new System.EventHandler(this.cmdUpdate_Click); flupp Fertig! So, ihr Lieben und jetzt das Ganze noch mal zusammenhängend für die Programmsammlung zuhause. Achja, das komplette Projekt ist als ZIP-Archiv ebenfalls verfügbar. Viel Spass! using System; using System.Drawing; using System.Diagnostics; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using System.IO; using System.Data; using System.Data.OleDb; namespace VfpBrowse { /// /// Zusammenfassung für frmVfpBrowse. /// public class frmVfpBrowse : System.Windows.Forms.Form { private string path; private string file; private OleDbDataAdapter da; private DataSet ds; private System.Windows.Forms.OpenFileDialog openFileDialog1; private System.Windows.Forms.Button cmdSelect; private System.Windows.Forms.Button cmdBrowse; private System.Windows.Forms.Button cmdUpdate; private System.Windows.Forms.DataGrid grdTable; private System.Windows.Forms.Label lblTable; private System.Windows.Forms.TextBox txtTable; /// /// Erforderliche Designervariable. /// private System.ComponentModel.Container components = null; public frmVfpBrowse() { // // Erforderlich für die Windows Form-Designerunterstützung // InitializeComponent(); this.grdTable.CurrentCellChanged += new System.EventHandler(this.cmdUpdate_Click); // // TODO: Fügen Sie den Konstruktorcode nach dem Aufruf von InitializeComponent hinzu // } /// /// Die verwendeten Ressourcen bereinigen. /// protected override void Dispose( bool disposing ) { if( disposing ) { if (components != null) { components.Dispose(); } } base.Dispose( disposing ); } #region Vom Windows Form-Designer generierter Code /// /// Erforderliche Methode für die Designerunterstützung. /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. /// private void InitializeComponent() { this.openFileDialog1 = new System.Windows.Forms.OpenFileDialog(); this.txtTable = new System.Windows.Forms.TextBox(); this.cmdSelect = new System.Windows.Forms.Button(); this.cmdBrowse = new System.Windows.Forms.Button(); this.cmdUpdate = new System.Windows.Forms.Button(); this.grdTable = new System.Windows.Forms.DataGrid(); this.lblTable = new System.Windows.Forms.Label(); ((System.ComponentModel.ISupportInitialize)(this.grdTable)).BeginInit(); this.SuspendLayout(); // // txtTable // this.txtTable.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.txtTable.Location = new System.Drawing.Point(167, 5); this.txtTable.Name = \"txtTable\"; this.txtTable.Size = new System.Drawing.Size(312, 20); this.txtTable.TabIndex = 0; this.txtTable.Text = \"\"; // // cmdSelect // this.cmdSelect.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.cmdSelect.Location = new System.Drawing.Point(486, 4); this.cmdSelect.Name = \"cmdSelect\"; this.cmdSelect.Size = new System.Drawing.Size(24, 23); this.cmdSelect.TabIndex = 1; this.cmdSelect.Text = \"...\"; this.cmdSelect.Click += new System.EventHandler(this.cmdSelect_Click); // // cmdBrowse // this.cmdBrowse.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); this.cmdBrowse.Location = new System.Drawing.Point(517, 4); this.cmdBrowse.Name = \"cmdBrowse\"; this.cmdBrowse.TabIndex = 2; this.cmdBrowse.Text = \"Browse\"; this.cmdBrowse.Click += new System.EventHandler(this.cmdBrowse_Click); // // cmdUpdate // this.cmdUpdate.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); this.cmdUpdate.Location = new System.Drawing.Point(8, 384); this.cmdUpdate.Name = \"cmdUpdate\"; this.cmdUpdate.TabIndex = 3; this.cmdUpdate.Text = \"Update\"; this.cmdUpdate.Click += new System.EventHandler(this.cmdUpdate_Click); // // grdTable // this.grdTable.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) | System.Windows.Forms.AnchorStyles.Left) | System.Windows.Forms.AnchorStyles.Right))); this.grdTable.DataMember = \"\"; this.grdTable.HeaderForeColor = System.Drawing.SystemColors.ControlText; this.grdTable.Location = new System.Drawing.Point(8, 32); this.grdTable.Name = \"grdTable\"; this.grdTable.Size = new System.Drawing.Size(584, 344); this.grdTable.TabIndex = 4; // // lblTable // this.lblTable.AutoSize = true; this.lblTable.Location = new System.Drawing.Point(8, 8); this.lblTable.Name = \"lblTable\"; this.lblTable.Size = new System.Drawing.Size(156, 16); this.lblTable.TabIndex = 5; this.lblTable.Text = \"Browse a Visual FoxPro table:\"; // // frmVfpBrowse // this.AutoScaleBaseSize = new System.Drawing.Size(5, 13); this.ClientSize = new System.Drawing.Size(600, 414); this.Controls.Add(this.lblTable); this.Controls.Add(this.grdTable); this.Controls.Add(this.cmdUpdate); this.Controls.Add(this.cmdBrowse); this.Controls.Add(this.cmdSelect); this.Controls.Add(this.txtTable); this.Name = \"frmVfpBrowse\"; this.Text = \"Browse Visual FoxPro DBFs\"; this.Load += new System.EventHandler(this.frmVfpBrowse_Load); ((System.ComponentModel.ISupportInitialize)(this.grdTable)).EndInit(); this.ResumeLayout(false); } #endregion /// /// Der Haupteinstiegspunkt für die Anwendung. /// [STAThread] static void Main() { Application.Run(new frmVfpBrowse()); } private void frmVfpBrowse_Load(object sender, System.EventArgs e) { } private void cmdSelect_Click(object sender, System.EventArgs e) { try { openFileDialog1.Filter = \"Visual FoxPro tables (*.dbf)|*.dbf\"; openFileDialog1.FilterIndex = 2; openFileDialog1.RestoreDirectory = true; } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } if(openFileDialog1.ShowDialog() == DialogResult.OK) { txtTable.Text = openFileDialog1.FileName; } } private void cmdBrowse_Click(object sender, System.EventArgs e) { try { this.path = Path.GetDirectoryName(txtTable.Text); this.file = Path.GetFileNameWithoutExtension(txtTable.Text); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } string ConnString = \"Provider=vfpoledb.1;Data Source=\" + this.path + \";Persist Security Info=false;\"; OleDbConnection Conn = new OleDbConnection(ConnString); try { Conn.Open(); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } try { this.da = new OleDbDataAdapter(\"SELECT * FROM \" + this.file, Conn); OleDbCommandBuilder cb = new OleDbCommandBuilder(this.da); da.InsertCommand = cb.GetInsertCommand(); da.UpdateCommand = cb.GetUpdateCommand(); da.DeleteCommand = cb.GetDeleteCommand(); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } this.ds = new DataSet(); this.ds.Clear(); try { this.da.Fill(this.ds); grdTable.DataSource = this.ds.Tables[0]; } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } } private void cmdUpdate_Click(object sender, System.EventArgs e) { if(this.ds.HasChanges()) { DataSet dc = this.ds.GetChanges(); try { this.da.Update(dc); this.ds.AcceptChanges(); } catch (Exception ex) { Debug.WriteLine(ex.ToString()); } } } } } if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'in-80-zeilen-um-die-vfp-tabelle.html'; this.page.identifier = '9271ed1c97_idin-80-zeilen-um-die-vfp-tabelle'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "index.html": {
    "href": "index.html",
    "title": "Get Blogged by JoKi - Get Blogged by JoKi",
    "summary": "Development Customise Your Instance of Jitsi Meet Explore practical options to customise and brand your self-hosted Jitsi Meet instance, and how to make modifications persistent across Debian and Ubuntu package upgrades. Jochen Kirstätter Jochen Kirstätter Sep 6, 2026 Security The Misconception of Disabling Paste on Password Fields Blocking clipboard paste on password fields does not thwart attackers. It sabotages password managers and ruins authentication security. Jochen Kirstätter Jochen Kirstätter Sep 3, 2026 AI From Solo Blogger to Editorial Board: How I Built a Multi-Agent Publishing Agency How a decade-long solo blogger assembled an 8-agent virtual publishing desk in Antigravity to conquer draft backlogs and enforce editorial discipline. Jochen Kirstätter Jochen Kirstätter Sep 2, 2026 Community Speaking at GDG Cloud Munich: State of GCP (.NET Edition) Ahead of the Google I/O Connect event in Amsterdam there had been exchange with the organisers of GDG Cloud Munich. Thankfully, they accepted a proposal to speak. Why Munich, you ask. It's quite far... Jochen Kirstätter Jochen Kirstätter Aug 31, 2026 Development Mastering the Matrix: Elegant QR Code Generation How to craft lightweight, high-performance QR code engines across Python, JavaScript, and modern C# on .NET 10, combining Galois field mathematics, Reed-Solomon Level H error correction, and frosted-glass centre badges. Jochen Kirstätter Jochen Kirstätter Aug 23, 2026 AI Using Antigravity Remote Control Exploring the experience of using Antigravity Remote Control on a mobile phone to review, edit, redact, and author draft articles directly within a local git repository. Jochen Kirstätter Jochen Kirstätter Aug 22, 2026 Development Using portless with Firebase Hosting A practical guide on combining portless local HTTPS reverse proxy with Firebase Hosting Emulators for frictionless local web development without memorizing port numbers. Jochen Kirstätter Jochen Kirstätter Aug 21, 2026 Development Gemma 3 - missing features despite announcement Congrats to the team to provide the new Gemma 3 models and the new endpoint on the Google AI API. The announcement blog - https://blog.google/technology/developers/gemma-3/ - reads wonderfully. Until... Jochen Kirstätter Jochen Kirstätter Mar 12, 2025 Development Gemini ResponseSchema vs JSON Schema specs of \"type\" as array Recently, while developing some changes in the Gemini SDK for .NET and given a reported issue on GitHub I had a look at the possibilities of passing an object as the expected structured output in JSON... Jochen Kirstätter Jochen Kirstätter Jan 20, 2025 Community C# Corner Annual Conference 2017 Speaking at the C# Conference 2017 in Delhi was an amazing experience. Both, the event and the exploration of Delhi. Visiting the Taj Mahal was a highlight. Jochen Kirstätter Jochen Kirstätter May 30, 2024 Development Untrack files in .gitignore Despite best effort and preparation of your precious source code one day you might be facing the situation that there is one or more files in your development workspace that doesn't belong into your git repository. Jochen Kirstätter Jochen Kirstätter Apr 5, 2024 Development Quickstart: Use Gemini with .NET (Vertex AI) Use Gemini in .NET in shortest time following this quickstart to configure Vertex AI on Google Cloud and add generative AI features to your apps. Jochen Kirstätter Jochen Kirstätter Apr 3, 2024"
  },
  "infopath-als-frontend-fr-blogs.html": {
    "href": "infopath-als-frontend-fr-blogs.html",
    "title": "InfoPath als Frontend für Blogs - Get Blogged by JoKi",
    "summary": "InfoPath als Frontend für Blogs Copy Markdown View Markdown GitHub Markdown Source InfoPath als Frontend für Blogs Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'infopath-als-frontend-fr-blogs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'infopath-als-frontend-fr-blogs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'InfoPath als Frontend für Blogs') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-22 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); InfoPath, CommunityServer, XML, blog software Mit dem Wechsel der serverseitigen Blogsoftware von .Text auf den CommunityServer auf https://www.foxite.com musste ich ebenfalls meine lokale InfoPath-Templates ändern. Glücklicherweise hat der Autor von SimpleBlogger seit längerem auch ein Template namens CSBlogger speziell für den CommunityServer bereitgestellt. Also, kurze Googlesuche, Download und wieder einrichten. Ist alles ein Kinderspiel und binnen 20 Minuten stehen auch wieder zwei neue Einträge in meinem englischsprachigen Blog online. Ein wesentlicher Vorteil bei der Verwendung von InfoPath statt dem Webbrowser besteht darin, dass die Einträge lokal gespeichert sind und die Anwendung offline funktioniert. Daher besteht in keinster Weise die Notwendigkeit einer Onlineverbindung. Was ziemlich interessant ist, wenn man im Zug oder sonstwie unterwegs ist. Für das Blog hier verwende ich einen anderen Ansatz. Ich schreibe die Beiträge zuerst in einer lokalen Installation des AfpWiki auf meiner Maschine und sobald der Artikel fertig ist, stelle ich den kompletten Eintrag online. Durch die Nutzung der BBCodes ist die Formatierung ebenfalls identisch. Lediglich die optische Präsentation divergiert ein kleines bisschen. Aber ansonsten keine Probleme. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'infopath-als-frontend-fr-blogs.html'; this.page.identifier = '9271ed1c97_idinfopath-als-frontend-fr-blogs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "infotech-2008.html": {
    "href": "infotech-2008.html",
    "title": "InfoTech 2008 - Get Blogged by JoKi",
    "summary": "InfoTech 2008 Copy Markdown View Markdown GitHub Markdown Source InfoTech 2008 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'infotech-2008'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'infotech-2008'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'InfoTech 2008') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-08-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The National Computer Board (NCB) invited to visit their annually InfoTech fair at the Swami Vivekananda International Convention Centre. This event marks the peak of IT sector here on the island and a lot of well-known companies showed their business stuff at the Exhibition Space. Actually, I had several chances to meet people from Microsoft Indian Ocean, the University of Mauritius and the University of Technology, Mauritius After all I spend some nice hours at the Convention Centre. Looking forward to Infotech 2009. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'infotech-2008.html'; this.page.identifier = '9271ed1c97_idinfotech-2008'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "infotech-2013.html": {
    "href": "infotech-2013.html",
    "title": "Infotech 2013 - Confusing and mixed impressions - Get Blogged by JoKi",
    "summary": "Infotech 2013 - Confusing and mixed impressions Copy Markdown View Markdown GitHub Markdown Source Infotech 2013 - Confusing and mixed impressions Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'infotech-2013'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'infotech-2013'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Infotech 2013 - Confusing and mixed impressions') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-11-22 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); These recent days are packed with a series of great events... Starting last Saturday, at the monthly meetup of the MSCC I talked about my experience having a conversation with representatives of the National Computer Board (NCB) about the feasibility of getting one of their presentation slots at the Infotech 2013. At that time, the overview for three days was completely (!) empty - with less than 10 days before the event and the presentations are about too happen! Well, short answer of the NCB: \"No, those slots are only for paying exhibitors. But I'll check with my manager and we'll call you back.\" Kind of disappointing for organisations and communities like the MSCC or the LUGM. I guess it is needless to say that I'm still waiting for their phone call... Anyway, Ish picked up the idea on last Saturday and also got in touch with the NCB via email. Most interestingly, his approach was way more fruitful and absolutely surprising and completely unexpected for any of us craftsmen he not only got one slot for topic presentations on Open Source, Linux and IT communities but three, and on top of that the NCB invited him to join them at their floor stand during the whole 4 days of Infotech to talk about Open Source. Fantastic and well done, Mr openSUSE advocate! Quick overview Since my first visit to Infotech a couple of years back, I have to admit that the whole event has gone from hardly acceptable to bad to absolutely unnecessary. Seriously, Infotech 2013 is not about latest technology and trends available here on the island... Infotech 2013 - the annual computer bazaar and discount market here on the island It's about lots of discounts and sell as much as you can. Empty the dust-covered shelves and sell it to the masses. Oh, you might day I'm too sarcastic about that? Hm, no I don't think so. Even when you have a conversation at some of the booths, and you ask for a couple of products being advertised - guess what? \"Well, we didn't receive the item yet. Maybe tomorrow afternoon.\" Compared to last year, there is not a single institution of tertiary education re-presented. Kind of shocking to my opinion. But again, I think this simply emphases the aspects of a tech-sales market rather than being a platform to show-case and demonstrate latest IT gadgets. Searching the dialog with the National Computer Board As mentioned, Ish got the offer from the NCB to talk about various topics using their available slots. First, as we were all too early we went to check the conference room - location, size, equipment, etc. All okay and next we tried to find someone in charge to get us going. Back at the NCB stand, it took quite some time until we got the right person. Anyway, nothing to complain or worry about as there was still plenty of time. Meanwhile, I kept myself occupied with an easy conversation about the NCB Technopreneurship Programme - what an ugly word creation - well, I kind of tried it. I guess, talking English and not looking Mauritian doesn't qualify you straight ahead to join and participate in the entrepreneurial business programme of the NCB. So, finally after at least three times rephrasing my questions I got part of the answers but still left out with the essential benefits of the whole programme itself. I mean, it's not like I would be in need to join their programme but I would like to transfer some of the information to other MSCC craftsmen which might pursue in founding a start-up in Mauritius. To cut things short: If I would be interested I might send a request for a presentation. Hm, honestly... I'm offering a communication platform to the NCB and it's up to them whether they would like to advertise and promote the Technopreneurship Programme to our 50+ craftsmen or not. Anyway, I madea note on our Trello workspace as a potential topic... Our test-run Despite the set time slots we decided to swap the sequence of our three presentations, so that they match more closely to Monday at the Linuxfest 2013. And I had to leave early, too. Running Windows applications on Linux Mauritius Software Craftsmanship Community openSUSE Project / openSUSE Advocate Program Our exclusively selected Infotech2013 session audience Kicking of the sessions, I went quickly through my slides and spent more time on the actual demonstration on how to run Windows software on Linux. Apart from a small hiccup in Microsoft Word 2007 Reading View all went smooth and stable. Overall it took me about 40 minutes to talk the various options and possibilities that could simplify your move from a Windows desktop to a Linux-based system. But for sure, I have to get rid off some visual effects on the slides and cut some demo material for Monday. I'm going to have 20 minutes only... Nice challenge! *Infotech 2013 - Running Windows applications on Linux * Infotech 2013 - Mauritius Software Craftsmanship Community presented by Nirvan and Nayar *Infotech 2013 - openSUSE Project and openSUSE Advocate Program presented by Ish * Luckily, I could stay some more minutes and enjoyed the presentation of Nirvan and Nayar about the MSCC. Both did a very good job, even though that the laptop used gave them a hard time. Ironically, it's a Windows system... okay, okay, just kidding! Afterwards some minor advice, like using a dedicated Windows user account only for the presentation (which avoids nasty notifications from the Systray area), and to uncheck \"Show Desktop Icons\" in order to show a clean and empty desktop background on the big screen. Hm, which actually reminds me to choose a better background image for Monday, too! Resume of the day Experimental... It's been a while since I spoke in public. Well, this time it was only a small audience but I'm really excited about what's going to happen at the Linuxfest 2013 on Monday. I'm really looking forward to an awesome experience and to meet lots and lots of enthusiastic young people. Updates I just went through the blog posting of Yashvin and his statement confirms my impressions neatlessly: \"I won’t be too long explaining in words how was the Infotech 2013 edition but in one word, I would say “Worst”.\" if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'infotech-2013.html'; this.page.identifier = '9271ed1c97_idinfotech-2013'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "innovtech-2017.html": {
    "href": "innovtech-2017.html",
    "title": "Infotech/InnovTech 2017 - Get Blogged by JoKi",
    "summary": "Infotech/InnovTech 2017 Copy Markdown View Markdown GitHub Markdown Source Infotech/InnovTech 2017 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'innovtech-2017'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'innovtech-2017'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Infotech/InnovTech 2017') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-02-03 - Filed under CommunityActivityGeneral (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Infotech or commonly known as the annual IT tech bazaar in Mauritius was looking for possibilities to change in 2017 - moving away from the dominant sales activities during the past years back to its original roots of being an innovative and informative IT event. And they did it! Infotech - the known parts I'm just going to quote their mission statement here: INFOTECH is the major annual Information and Communication Technology (ICT) event organised in Mauritius by the National Computer Board (NCB) in collaboration with the Ministry of Technology, Communication and Innovation. The aim is to create awareness on emerging technologies and provide business opportunities in the ICT sector. Personally, I went to Infotech first time in 2008, and back then there were more product demonstration than a vast range of discounts. And yes, every year Infotech is surely offering business opportunities and people are looking forward to it. Given the annual dates around end of November or begin of December it takes somehow into consideration that a good number of people in the IT sector and in general already received their annual bonus payments. What else better than to spend it on some hardware gadgets like a new mobile phone, a large smart TV, a new computer or laptop? But unfortunately that seemed to have been one of the major reasons why the reputation of Infotech as an event to create awareness on emerging technologies has lost out. There was too much emphasis on sales and too few on innovation. Eventually, this might be an opinionated view of things on my side. Would be great to have your input in the comments below. Infotech - the changed parts Interestingly, Infotech 2017 had less booths of retailers than in previous years and there were a good number of technology representatives. Among them someone advertising the possibilities that Mauritius could go for its own satellite in space. How cool would that be? Source: https://www.facebook.com/InfotechMauritius/ Also the organisers arranged the available exhibition space into several zones, most remarkable a designated area for 3D printing only. Then there was the LAN gaming zone strategically well placed at the back of the convention centre. And many more others. The foyer area had also more to offer than the usual food court. Most outstanding change was that the booth of the National Computer Board sprung right in your face at the entry. Fantastic placement and so easy to reach for every visitor. Really loved it. Like in previous years the NCB offered part of their booth to the Linux User Group of Mauritius (LUGM) to advocate for Free and Open Source Software (FOSS). Linux User Group of Mauritius (LUGM) to advocate for Free and Open Source Software (FOSS) The guys of the LUGM did a great job during those few days and the demonstration of Linux inclusive standard software like office, email, browser, graphics, etc. as well as show casing a complete multimedia centre running on the TV, openElec if I remember correctly, provided a solid overview and insight to visitors that there is more than just Windows for their computers. Also in the foyer there was a booth run by students of the University of Mauritius. Here they demonstrated some of their final year assignments in action. My children were really into it and glad that they were allowed to literally try everything. Final year assignments and projects by students of the University of Mauritius Interesting prototypes of robotic hands - even with remote controlled operation mode Check out more Photos section on the Infotech page on Facebook. InnovTech - the new parts InnovTech Conference, a new format of Infotech or let's better say an innovative complimentary to Infotech was organised the first time during the 2017 event. And I'm grateful about this unique opportunity to have worked with the National Computer Board to bring this new 'mini conference' format to Infotech. Mr. Jochen Kirstätter, the founder of MSCC [note: Mauritius Software Craftsmanship Community], and the Ministry of TCI [note: Ministry of Technology, Communication and Innovation] will take the lead for Day 2 and Day 3 of Innovtech Conference. Eventually inspired by the annual Developers Conference of the Mauritius Software Craftsmanship Community the NCB got in touch with several representatives of local IT communities, and invited us to several meetings in regards to InnovTech. It was a great experience, and I'm glad that some of our ideas were taken into consideration. Approximately 280 attendees came to InnovTech During Infotech the convention centre is completely used by the NCB but in the past years the various conference rooms upstairs were given no importance or value. By helping to create and to organise InnovTech I think (and hope) that future events will offer greater value to all visitors. According to officials of the National Computer Board we were able to attract approximately 280 attendees to come and follow the various presentations of InnovTech during those two days. More to read... Probably, this article might be a touch too biased given my involvement in InnovTech. Therefore I would like to invite you to read what other attendees and speakers of Infotech 2017 had to say about the event. My interactive session on Innovation during Infotech / Innovtech 2017 by Avinash Meetoo, Advisor to the Minister of TCI Infotech 2017: la vision 2030 pour le secteur des TIC en ligne de mire by Florian Lepoigneur, Lexpress.mu Infotech 2017: les imprimantes 3D volent la vedette by Florian Lepoigneur, Lexpress.mu Eagerly expecting Infotech/InnovTech 2018 I have to admit that the combination of exhibition area on the ground floor with the designated technology zones and the informative presentations in the Kestrel conference room on first level gave Infotech 2017 a positive twist. The National Computer Board managed to put new, fresh life into the event. Looking forward to this year's Infotech / InnovTech. How about you? Did you check out the exhibits or eventually did you attend one or more presentations? What's your opinion of Infotech 2017 compared to previous years? PS: Yeah, my children raided the Cyber Caravan to do another Hour of Code like professionals. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'innovtech-2017.html'; this.page.identifier = '9271ed1c97_idinnovtech-2017'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "install-adobe-air-on-ubuntu-linux.html": {
    "href": "install-adobe-air-on-ubuntu-linux.html",
    "title": "Install Adobe AIR on Ubuntu/Linux - Get Blogged by JoKi",
    "summary": "Install Adobe AIR on Ubuntu/Linux Copy Markdown View Markdown GitHub Markdown Source Install Adobe AIR on Ubuntu/Linux Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'install-adobe-air-on-ubuntu-linux'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'install-adobe-air-on-ubuntu-linux'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Install Adobe AIR on Ubuntu/Linux') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-03-19 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Since quite some time Adobe Technologies released the Linux version of Adobe AIR to bring web applications and widgets to your desktop. Installing new applications on a Linux system is not always as easy as switching the computer on. The following instructions might be helpful to install Adobe AIR on any Linux system. First of all, get the latest installer of Adobe AIR from https://get.adobe.com/air/ - as of writing this article the file name is AdobeAIRInstaller.bin. Save the download in your preferred folder. Now, there are two ways to run the installer - visual style or console style. Visual Installation Launch your favorite or standard file manager like thunar or nautilus and browse to the folder where the AdobeAIRInstaller.bin has been saved. Right click on the file and choose 'Properties' in the context menu Set 'Execute' permissions and confirm modifications with OK Rename file into AdobeAIRInstaller Double click and follow the instructions Using the console Open a terminal like xterm Change into the directory where you stored the download Run this command: chmod +x AdobeAIRInstaller.bin Now run this command: sudo ./AdobeAIRInstaller.bin The normal installer will open, install it. From now whenever you download a .air file, just double click it and it will be installed. Troubleshooting In case that the installation does not start properly, try to install via console. This gives you more details about the reasons. Should you run into something like this: AdobeAIRInstaller.bin: 1: Syntax error: \"(\" unexpected Double check the execute permission of the installer file and try again. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'install-adobe-air-on-ubuntu-linux.html'; this.page.identifier = '9271ed1c97_idinstall-adobe-air-on-ubuntu-linux'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "install-jitsi-meet-on-gcp.html": {
    "href": "install-jitsi-meet-on-gcp.html",
    "title": "Install Jitsi Meet on Compute Engine (GCP) - Get Blogged by JoKi",
    "summary": "Install Jitsi Meet on Compute Engine (GCP) Copy Markdown View Markdown GitHub Markdown Source Install Jitsi Meet on Compute Engine (GCP) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'install-jitsi-meet-on-gcp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'install-jitsi-meet-on-gcp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Install Jitsi Meet on Compute Engine (GCP)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-04-16 - Filed under DevelopmentCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Irrespective of the current situation I already had a look at the Jitsi Meet platform some time back last year. Due to a lack of necessity and organising most community meetings offline - both MSCC and GDG Mauritius - there was no motivation to look closer into any video conferencing platform. Inspired by the announcement that meet.mixp.org offers free access to host video conferencing calls locally in Mauritius and the fact that past few meetings of the MSCC had been conducted virtually using Google Hangouts Meet, I reserved some time to do a little research. The result is a series of tutorials on how to get started with Jitsi Meet, how to customise it for your own branding, and how to enable more features beyond the basic installation. This first article describes the fundamental installation of Jitsi Meet on the Google Cloud Platform. Surely, the necessary steps shall be reproducible on other cloud computing platforms like Microsoft Azure or Amazon AWS. Prerequisites: Domain Name Before you start following the steps in this article you should consider to have a fully qualified domain name (FQDN) at hand. That domain will be used to access your Jitsi Meet server over the internet. I'm going to use the subdomain meet.mscc.mu because the Jitsi Meet video conferencing system described here will be used for the Mauritius Software Craftsmanship Community (MSCC) and associated user groups in Mauritius. Ready? Let's log into Google Cloud Console and begin the installation. Define a permanent External IP address First, you reserve a static IP address. We are going to use this IP address for the DNS record of your FQDN and to attach it to the VM instance running Jitsi Meet server. In Cloud Console navigate to VPC network > External IP addresses and reserve a new static address. Although the IP address is not attached to anything yet we are going to use it shortly. The result should look similar to this. Of course, your external IP address will be different. Create a DNS entry Now, packed with the newly created IP address it is time to create a DNS record for the domain name you would like to use for the Jitsi Meet server. This step depends on your DNS nameserver provider. I'm going to describe how it is done using Cloudflare. Under DNS management create a new A record with the subdomain meet and the external IP address provided by Google Cloud Platform. The resulting DNS configuration should look similar to above. Global DNS propagation can take up to 24 hours and it is important to wait that your DNS record has been deployed successfully. Verify DNS configuration You can use any DNS query tool to verify this step. Depending on your OS either use dig or nslookup to check whether the DNS record has been distributed to the Cloudflare public DNS server. $ dig meet.mscc.mu @1.1.1.1 ; <<>> DiG 9.11.3-1ubuntu1.11-Ubuntu <<>> meet.mscc.mu @1.1.1.1 ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43365 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 ;; OPT PSEUDOSECTION: ; EDNS: version: 0, flags:; udp: 1452 ;; QUESTION SECTION: ;meet.mscc.mu. IN A ;; ANSWER SECTION: meet.mscc.mu. 300 IN A 35.232.25.165 > nslookup meet.mscc.mu 1.1.1.1 Server: one.one.one.one Address: 1.1.1.1 Non-authoritative answer: Name: meet.mscc.mu Address: 35.232.25.165 With proper DNS settings in place you are ready to continue with the installation of Jitsi Meet. Create a VM instance Log into Cloud Console and navigate to Compute Engine > VM instances. There click on Create instance and enter relevant information for your new VM instance. The default values provided by GCP shall work just fine. However you might like to adjust region, zone and machine configuration according to your liking. Specify the hostname Important: The initial configuration allows you to set a custom hostname for your instance and you should specify the prepared DNS name as such. This choice is permanent and cannot be changed later. Click on Management, security, disks, networking, sole tenancy and enter your FQDN under Hostname. Choose the External IP address While you are at the details of Networking click on the pen symbol of the Network interface and select the external IP address that we created earlier. The entry shall look like so. Click Done when your networking options are complete. If ever you missed the initial creation of an external IP address you can open the dropdown selection under External IP and choose to Create IP address. Give the new static IP address a name and click on Reserve. However this still requires proper DNS configuration as described earlier. Configure firewall rules According to the quick install guide Jitsi Meet requires the following ports and protocols to allow traffic from the internet. 80 TCP - aka HTTP traffic 443 TCP - aka HTTPS traffic 10000 UDP The first two ports can be configured during the creation of the VM instance. Tick the checkboxes in the Firewall section and the necessary rules will be applied during the creation of the virtual machine. For the UDP port you are going to enter a Network tag for the moment. That tag is used to connect the VM instance to a (new) firewall rule. Finally, click on Create to complete your VM instance. This is going to take a few seconds and you will be notified as soon as the VM instance is ready. Create and verify firewall rules In order to create the third, remaining firewall rule you navigate to VPC network > Firewall rules and there you click on Create Firewall Rule to configure the missing information. Under Target tags you enter the same value you used as Network tags during the configuration of the VM instance. This closes the link between the instance and this firewall rule. The value for the Source IP ranges is 0.0.0.0/0 which represents any IP address from the internet. Under Protocols and ports you tick UDP protocol and you enter the port number 10000. Finally, click on Create to enable the firewall rule. The result should look similar to the list of rules below. Access the VM instance Navigate back to Compute Engine > VM instances and click on the SSH button of your instance. This should open a new browser window. Note: Eventually you have to allow Cloud Console to open popups first. After a short initialisation the connection should be established and you are greeted by a bash prompt on Linux. Install Jitsi Meet software The following steps are based on the official Jitsi Meet quick install guide on GitHub - with a few additional notes and modifications on my side. Check hostname First, verify the assignment of the Fully Qualified Domain Name (FQDN) with the following command: $ hostname -f meet.mscc.mu At the same time check that the name resolution has been added to the hosts file. $ cat /etc/hosts 127.0.0.1 localhost ::1 localhost ip6-localhost ip6-loopback ff02::1 ip6-allnodes ff02::2 ip6-allrouters 10.128.0.2 meet.mscc.mu meet # Added by Google 169.254.169.254 metadata.google.internal # Added by Google Run commands as user root Note: Many of the installation steps require elevated privileges. If you are logged in using a regular user account, you may need to increase your permissions. Either use sudo for individual commands or temporarily change user context and operate as user root. $ sudo -i Although not recommended it's faster to complete the installation as root. Add the Jitsi package repository echo 'deb https://download.jitsi.org stable/' >> /etc/apt/sources.list.d/jitsi-stable.list wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | sudo apt-key add - The Jitsi repository uses a secured URL which requires that you add the HTTPS transport option to apt, then you update the local repository cache, and finally you install the package of Jitsi Meet and all dependencies. # Ensure support is available for apt repositories served via HTTPS apt-get install -y apt-transport-https # Retrieve the latest package versions across all repositories apt-get update # Perform jitsi-meet installation apt-get install -y jitsi-meet During the process you will be asked to enter the FQDN or hostname of your instance of Jitsi Meet. Enter the hostname that we verified already and hit OK to continue the installation. Next, you will be ask to configure SSL certificate of your Jitsi Meet domain. Here keep the default selection to generate a self-signed certificate and hit OK. We are going to generate an SSL certificate provided by Let's Encrypt in the next step. Install Let's Encrypt certificate Note: Verify that the DNS record has been assigned to your static external IP address and it has been distributed globally before you try to apply the Let's Encrypt SSL certificate. See Verify DNS configuration above on how to do that. At the time of writing there was a small issue running the script as described on GitHub: Lets Encrypt setup error about missing file #5929. The workaround is to create the expected deployment hook manually yourself before running the certificate installation script. # Workaround for missing deployment script mkdir -p /etc/letsencrypt/renewal-hooks/deploy/ touch /etc/letsencrypt/renewal-hooks/deploy/0000-coturn-certbot-deploy.sh chmod +x /etc/letsencrypt/renewal-hooks/deploy/0000-coturn-certbot-deploy.sh Now, run the following shell script as mentioned in the quick install guide. /usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh The script is going to ask you for an email address to send notifications from Let's Encrypt to. Then your terminal is going to provide you with tons of information and the outcome should look similar to below. Look for Congratulations! to be sure that the SSL certificate has been successfully requested from Let's Encrypt and applied to your system. Basically your installation is complete now. You shall be able to load your FQDN in your browser. In case you run into any trouble kindly repeat the steps above or refer to the official Jitsi Meet quick install or the Jitsi Community Forum. However there are probably some additional considerations prior to operating your instance of Jitsi Meet server on the internet. Adjust nginx configuration file The default configuration of nginx created by the Jitsi Meet package is not optimal and you should make a few changes to it. Open the file with your preferred text editor nano /etc/nginx/sites-enabled/meet.mscc.mu.conf Change the default protocol to HTTP/2 Look for the listen directives and add the value http2 at the end of both. It shall look like so. server { listen 443 ssl http2; listen [::]:443 ssl http2; ... Change SSL protocol versions and ciphers By default nginx is enabled to support TLS 1.0, TLS 1.1 and TLS 1.2. However the two former protocols are legacy protocol that shouldn't be used. TLS v1.0 and TLS v1.1 have been deprecated in January 2020 by modern browsers. Hence you should change the ssl_protocols directive like so. # ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_protocols TLSv1.2; Depending on the nginx version TLS 1.3 might be an additional option. You might consider to replace the existing directive of SSL ciphers with the following value. ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; The Mozilla SSL Configuration Generator is definitely worth a look. The Intermediate configuration would be the recommended choice. Perhaps you might like to read SSL and TLS Deployment Best Practices for more background information. Add more HTTP headers Next, you should define a few more HTTP header directives to improve your default configuration. Open the nginx config file again and add the following lines right after the existing add_header directive related to HSTS. add_header X-Content-Type-Options nosniff; # Don't use with iFrames, e.g. Jitsi Meet Desktop (Electron) app add_header X-Frame-Options SAMEORIGIN; add_header X-XSS-Protection \"1; mode=block\"; add_header Referrer-Policy no-referrer-when-downgrade; Note: The Jitsi Meet Electron application for desktop systems cannot load your instance if the HTTP header X-Frame-Options has been set. Either comment or remove that directive if you are planning to use the application. Save the configuration after each change and run a configtest. service nginx configtest [ ok ] Testing nginx configuration:. If the result is OK restart nginx as usual. Otherwise, inspect the log file located at /var/log/nginx/error.log for any error entries. service nginx restart In case that you are interested to see the impact of your changes open the Qualys SSL Server Test and validate your domain. Increase number of processes and open files The quick install guide mentions that the default configuration of a system is good for less than 100 participants. To avoid running into any unexpected situations I suggest that you increase that value already now. Open the file /etc/systemd/system.conf and add the following lines at the end. DefaultLimitNOFILE=65000 DefaultLimitNPROC=65000 DefaultTasksMax=65000 Reload the systemd changes on a running system and restart your Jitsi instance with those two commands. systemctl daemon-reload service jitsi-videobridge2 restart To verify that the settings have been applied run the following command and check the value of Tasks: XX (limit: 65000). service jitsi-videobridge2 status ● jitsi-videobridge2.service - Jitsi Videobridge Loaded: loaded (/lib/systemd/system/jitsi-videobridge2.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2020-04-16 11:23:08 UTC; 14s ago Process: 7896 ExecStartPost=/bin/bash -c echo $MAINPID > /var/run/jitsi-videobridge/jitsi-videobridge.pid (code=e xited, status=0/SUCCESS) Main PID: 7895 (java) Tasks: 38 (limit: 65000) ... Confirm your installation is working Open a new browser tab or better an incognito window and navigate to the FQDN you specified during the installation. You shall be greeted by the Jitsi Meet default page. Congratulations! When you click on the gear symbol in the top right corner your browser should ask for permissions to access microphone and camera. Click Allow in both cases and continue to configure your devices you would like to use in Jitsi Meet. Change to the tab Profile to provide more information like your displayed name in the meeting rooms. On the tab More you are able to configure your preferred language. Finally, enter any value to start a new meeting and click on GO. The use of camel-case writing forms the URL to access the meeting and is automatically converted into blanks after you entered the meeting room. Enjoy your very own Jitsi Meet video conferencing system. Automate it with gcloud All steps above can be executed by using gcloud commands. Best might be to use your instance of Cloud Shell to create a VM instance and to install Jitsi Meet. Make sure that you have a domain name prepared. Create the infrastructure You would probably adjust the environmental variables at the top to change region, zone, instance name and your DNS hostname. # Define your preferences and values - to CHANGE! export REGION=us-central1 export ZONE=us-central1-a export VM_INSTANCE=mscc-jitsi-dummy export FQDN=meet.mscc.mu # Enable Compute Engine gcloud services enable compute.googleapis.com # Reserve and retrieve static IP address gcloud compute addresses create $VM_INSTANCE-ip --project=$DEVSHELL_PROJECT_ID --description=\"Static external IP address for Jitsi Meet VM instance\" --region=$REGION export IP=$(gcloud compute addresses describe $VM_INSTANCE-ip --region=$REGION --format='get(address)') # Create VM instance with hostname and attach IP address gcloud beta compute --project=$DEVSHELL_PROJECT_ID instances create $VM_INSTANCE --hostname=$FQDN --zone=$ZONE --machine-type=n1-standard-1 --subnet=default --address=$IP --network-tier=PREMIUM --tags=jitsi,http-server,https-server --image=debian-9-stretch-v20200309 --image-project=debian-cloud --boot-disk-size=10GB --boot-disk-type=pd-standard --boot-disk-device-name=$VM_INSTANCE --reservation-affinity=any # Attach firewall rules (might be already present, no big deal) gcloud compute --project=$DEVSHELL_PROJECT_ID firewall-rules create default-allow-http --direction=INGRESS --priority=1000 --network=default --action=ALLOW --rules=tcp:80 --source-ranges=0.0.0.0/0 --target-tags=http-server gcloud compute --project=$DEVSHELL_PROJECT_ID firewall-rules create default-allow-https --direction=INGRESS --priority=1000 --network=default --action=ALLOW --rules=tcp:443 --source-ranges=0.0.0.0/0 --target-tags=https-server gcloud compute --project=$DEVSHELL_PROJECT_ID firewall-rules create default-allow-10k --description=\"Allow UDP packets for VM instance running Jitsi Meet server\" --direction=INGRESS --priority=1000 --network=default --action=ALLOW --rules=udp:10000 --source-ranges=0.0.0.0/0 --target-tags=jitsi # Display external IP address and connect to instance using SSH echo $IP gcloud compute ssh $VM_INSTANCE --zone=$ZONE After execution you are going to see the external IP address in the Shell, and you should be connected to the new VM instance. In case that the remote VM instance does not respond to the SSH connection or times out, wait a short while and repeat the last command to SSH into the instance. Now is the right time to verify that your DNS record is up-to-date and matches the external IP address of your VM instance before you continue to install Jitsi Meet. Install Jitsi Meet server This paragraph summarizes the commands used above to install Jitsi Meet server. They should be run as user root. Change into an interactive session of user root first. sudo -i Then run the following to complete the basic installation of Jitsi Meet server on the VM instance. # Increase number of processes and open files cat >> /etc/systemd/system.conf <<EOF DefaultLimitNOFILE=65000 DefaultLimitNPROC=65000 DefaultTasksMax=65000 EOF systemctl daemon-reload # Add the package repository echo 'deb https://download.jitsi.org stable/' >> /etc/apt/sources.list.d/jitsi-stable.list wget -qO - https://download.jitsi.org/jitsi-key.gpg.key | sudo apt-key add - # Ensure support is available for apt repositories served via HTTPS apt-get install -y apt-transport-https # Retrieve the latest package versions across all repositories apt-get update # Perform jitsi-meet installation apt-get install -y jitsi-meet Last, prepare the system for an SSL certificate provided by Let's Encrypt by running the following commands. Note: Without properly configured DNS this is going to fail. # Workaround for missing deployment script mkdir -p /etc/letsencrypt/renewal-hooks/deploy/ touch /etc/letsencrypt/renewal-hooks/deploy/0000-coturn-certbot-deploy.sh chmod +x /etc/letsencrypt/renewal-hooks/deploy/0000-coturn-certbot-deploy.sh # Get SSL certificate from Let's Encrypt /usr/share/jitsi-meet/scripts/install-letsencrypt-cert.sh You have to specify an email address to receive notifications regarding your certificates from Let's Encrypt. Congrats, your Jitsi Meet server is now operational. Maybe you like to review the nginx changes described above to improve your setup a little bit. Securing your Jitsi Meet server The default installation in this article is kind of basic and provides you a jumpstart to run your own video conferencing system. In the next article of this series I'm going to describe how you enable authentication and secure your Jitsi Meet instance. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'install-jitsi-meet-on-gcp.html'; this.page.identifier = '9271ed1c97_idinstall-jitsi-meet-on-gcp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "install-visual-studio-code-on-linux-ubuntu.html": {
    "href": "install-visual-studio-code-on-linux-ubuntu.html",
    "title": "Installing Visual Studio Code on Linux (Ubuntu) - Get Blogged by JoKi",
    "summary": "Installing Visual Studio Code on Linux (Ubuntu) Copy Markdown View Markdown GitHub Markdown Source Installing Visual Studio Code on Linux (Ubuntu) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'install-visual-studio-code-on-linux-ubuntu'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'install-visual-studio-code-on-linux-ubuntu'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Installing Visual Studio Code on Linux (Ubuntu)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-07-24 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); During this year's //build conference Microsoft officially announced a new member of the Visual Studio series called Code. As described by several people already it is a HTML5, JavaScript/TypeScript based text editor hosted inside the Electron shell and it runs natively on Windows, Mac OS X and Linux. This article hopefully gives you some ideas during installation and assistance to have an improved experience out of the box compared to the standard option - at least at the time of writing this article. Getting Visual Studio Code I started using Visual Studio Code since the first released version 0.1.0, and being part of the Insider Preview program for VS Code I managed to download and get the latest version always using this short-listed link: https://aka.ms/vscode Which is an alias for this web address: https://code.visualstudio.com/ Get the latest version of Visual Studio Code from the web site Microsoft's web site of Code detects your operating system and directly offers you the best download option based on your current browser. I'm currently running Xubuntu 15.04 x64 - Vivid Vervet and the site offers me a direct link to get the latest 64-bit version of Visual Studio Code. In case that you'd like to download a different version please scroll down to the bottom of the site and check the additional options. Note: Originally, I started using Code 0.1.0 on Xubuntu 14.10 and then upgraded my machine around mid of May. Also, on a different machine running Ubuntu 14.04 LTS I can confirm to use Visual Studio Code successfully. Unzip the archive After you downloaded the latest ZIP archive for your architecture, here: VSCode-linux-x64.zip, you should decide where to extract the content of the compressed file. Well, in my case, I'd like to have third party products below the appropriate location, and therefore I usually choose /opt. Eventually you might ask yourself why? Well, here's a decent chapter about the Linux Filesystem Hierarchy written by The Linux Documentation Project (TLDP): 1.13 /opt This directory is reserved for all the software and add-on packages that are not part of the default installation. For example, StarOffice, Kylix, Netscape Communicator and WordPerfect packages are normally found here. To comply with the FSSTND, all third party applications should be installed in this directory. Any package to be installed here must locate its static files (ie. extra fonts, clipart, database files) must locate its static files in a separate /opt/'package' or /opt/'provider' directory tree (similar to the way in which Windows will install new software to its own directory tree C:\\Windows\\Progam Files\"Program Name\"), where 'package' is a name that describes the software package and 'provider' is the provider's LANANA registered name. Looks good to me, or? Anyway, let's just use this as base - given that you're root on the machine - it's surely a good choice, otherwise feel free to unzip the archive in your personal user space below your home directory. Next, let's extract the content as suggested using the console (or terminal in case that you'd prefer this term): $ cd /opt /opt$ sudo unzip ~/Downloads/VSCode-linux-x64.zip This is going to create a new directory VSCode-linux-x64 which contains the static binary to run Visual Studio Code on your system. Right now, you would be able to launch the text editor by executing the following command: /opt$ ./VSCode-linux-x64/Code Despite some warnings and errors on the console output, similar to those: [3437:0724/220852:ERROR:browser_main_loop.cc(173)] Running without the SUID sandbox! See https://code.google.com/p/chromium/wiki/LinuxSUIDSandboxDevelopment for more information on developing with the sandbox on. bash: cannot set terminal process group (-1): Inappropriate ioctl for device bash: no job control in this shell Visual Studio Code is up and running... Welcome screen of Visual Studio Code on first start of the text editor Adding a little bit more comfort Hopefully, you were able to launch Visual Studio Code based on the description given above. Now, let's add a little bit more comfort to your user experience. Unfortunately, there is no out-of-the-box installation package for the usual distributions - at least not yet, and we are obliged to do some manual steps. Following, I'm going to give you my steps with some brief explanations about the why and how. Of course, there are always multiple choices and you might either skip one or the other step or even have better suggestions. Please use the comment section at the bottom to give me your tips & tricks. Thanks! Version-(in)dependent folder and symbolic link Not sure about you but given the manual installation steps I would like to have a better control each time I consider to install a newer version of Code. Also, this helps to keep some adjustments on constant path information like Application launcher and shortcuts to run Visual Studio Code. Okay, let's dig into that and first rename (move) the base directory of Code to a version-specific one: /opt$ sudo mv VSCode-linux-x64 VSCode-0.5.0 Again, as of writing this article 0.5.0 was the latest available version. Meanwhile, the are good chances that you might have a higher version already - good! Next, I usually create a symbolic soft link back to the newly renamed folder in order to stay version-independent. Sounds confusing, right? Hold on, I'll explain it in a short, and you will see the benefits, too. /opt$ sudo ln -s VSCode-0.5.0 VSCode Your own /opt folder might look similar to this one right now: Extract the Visual Studio Code zip archive below /opt directory and create a version-independent symlink As you can see on the screenshot I've been using Code since the very beginning, and using this approach I am actually able to keep all versions installed side-by-side next to each other. The most interesting part is the version-independent symlink in the /opt directory. This allows me to launch Visual Studio Code by executing the following line from anywhere: /opt/VSCode/Code Like using the Application Finder on Xubuntu after pressing Alt+F2: Launch Visual Studio Code from the Application Finder with fully qualified path to executable This scenario gives us a good head start for further activities. The power of PATH Now that we have a \"fixed\" location for Visual Studio Code, it would be more comfortable to avoid to specify the full path information each time that we would like to launch the text editor. Also, looking to some of the cool command line options of Code on other platforms, it would be nice to have them as well on Linux. Okay, then let's do it using the PATH environment variable. The Linux Information Project has a good definition online: PATH Definition PATH is an environmental variable in Linux and other Unix-like operating systems that tells the shell which directories to search for executable files (i.e., ready-to-run programs) in response to commands issued by a user. It increases both the convenience and the safety of such operating systems and is widely considered to be the single most important environmental variable. That sounds exactly like what we are looking for. And in compliance with other operating systems, we are going to create another symlink for our purpose, like this: ~$ sudo ln -s /opt/VSCode/Code /usr/local/bin/code Changing the letter casing of the executable from proper writing - Code - to lower case writing - code - isn't a typo actually. Update: Recently, I discovered that the official guide on Setting up Visual Studio Code on Linux also mentions the creation of a symlink as a tip. Commonly, UNIX and Linux commands are written in lower-case writing anyway, so why should we break with this tradition? Of course, you will be able to launch the text editor now with this new path, too. Either on the console / terminal, like so ~$ code or using the Application Finder - the choice is yours. Launch Visual Studio Code from the Application Finder Thanks to the PATH environment variable we can now completely omit the path information. Linux knows where to find our executable now. Application launcher in Main Menu Being able to start Visual Studio Code anywhere from the console has already given us some comfort but compared to Windows and Mac OS X users we are still living in the digital stone age, and no application is fully installed on your Linux OS without an application launcher in your main menu. In Xubuntu you would open Application Menu (or press Alt+F1) - Settings - Main Menu in order to add a new launcher to the menu. In the menu editor select the Development section or any other where you would like to place the launcher and click on New Item to define the Launcher Properties. Eventually, you might like to enter the following on your machine: Add a new item to the main menu for Visual Studio Code Unfortunately, this leaves us with an empty icon for now. Quickly open a new terminal and switch to an existing one and let's see which graphics are provided by Microsoft, like so: ~$ find /opt/VSCode/* -type f -iname '*.png' /opt/VSCode/resources/app/vso.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-up.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-left.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-right.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-right-dark.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-left-dark.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-down-dark.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-down.png /opt/VSCode/resources/app/client/vs/base/ui/scrollbar/impl/arrow-up-dark.png /opt/VSCode/resources/app/client/vs/editor/diff/diagonal-fill.png /opt/VSCode/resources/app/client/vs/editor/css/arrow-left.png /opt/VSCode/resources/app/client/vs/editor/css/arrow-right.png /opt/VSCode/resources/app/client/vs/workbench/contrib/daytona/TestPlugin/Resources/Images.png /opt/VSCode/resources/app/client/vs/workbench/contrib/daytona/TestPlugin/Images/FileIdentifier.png /opt/VSCode/resources/app/client/vs/workbench/contrib/daytona/TestPlugin/Images/icon2.png /opt/VSCode/resources/app/client/vs/workbench/contrib/daytona/TestPlugin/Images/icon3.png /opt/VSCode/resources/app/client/vs/workbench/contrib/daytona/TestPlugin/Images/icon1.png /opt/VSCode/resources/app/client/vs/workbench/contrib/daytona/TestPlugin/Images/console-icons.png /opt/VSCode/resources/app/client/vs/workbench/ui/parts/editor/media/letterpress.png /opt/VSCode/resources/app/client/vs/workbench/ui/parts/editor/media/letterpress-dark@2x.png /opt/VSCode/resources/app/client/vs/workbench/ui/parts/editor/media/letterpress-dark.png /opt/VSCode/resources/app/client/vs/workbench/ui/parts/editor/media/letterpress@2x.png /opt/VSCode/resources/app/node_modules/emmet/Icon.png Alternatively, you might also have a look at the SVG graphics provided by Visual Studio Code. I chose the vso.png and to simplify my life in regards of future upgrades and unexpected changes, I placed a copy of the graphic file into the usual location on a Linux system: ~$ sudo cp /opt/VSCode/resources/app/vso.png /usr/share/icons/ Hint: Use the Move option in the window menu to relocate the dialog using the arrow keys, and then confirm your selection with a click on the OK button of the dialog. Your Main Menu editor might look like this now: Visual Studio Code as proper entry in the main menu of Xubuntu Congratulations, your new application launcher has been added to the menu and you can either navigate into the Development section (or the one you chose) or type your choice into the application quick filter textbox to find and execute Visual Studio Code. Navigate the application menu to launch Visual Studio Code Use the quick filter entry of the application menu to launch Visual Studio Code Creating a Desktop Entry file As we are working with Linux there are always multiple ways to achieve the same or similar result. And eventually you might prefer the possibility to create and use a file-based application launcher which adds itself to the menu structure automatically. Creating a .desktop file is not too challenging and requires a simple text editor - like Visual Studio Code \uD83D\uDE09 - to write the following definition into it: [Desktop Entry] Version=1.0 Encoding=UTF-8 Name=Visual Studio Code GenericName=Integrated Development Environment Comment=Code Editing. Redefined. Build and debug modern web and cloud applications. Exec=code TryExec=code Icon=vso StartupNotify=true Terminal=false Type=Application MimeType=text/x-csharp;application/x-mds;application/x-mdp;application/x-cmbx;application/x-prjx;application/x-csproj;application/x-vbproj;application/x-sln;application/x-aspx;text/xml;application/xhtml+xml;text/html;text/plain; Categories=GNOME;GTK;Development;IDE; Save it as vscode.desktop and then put this file into the appropriate location for a Linux system: ~$ sudo cp vscode.desktop /usr/share/applications/vscode.desktop Thanks to the proper location of the shared icon and the symlinks we created earlier, we do not have to specify any absolute paths in our Desktop Entry file. As soon as the file has been copied below the shared applications folder it automatically appears in your main menu and is ready to be used. For your extra comfort you might like to download the vscode.desktop file. You will have to rename the file and place it accordingly on your system. Make it a launcher in Cairo Dock As for the different options of Ubuntu I have to admit that I'm a long-year user of the Xfce environment, called Xubuntu, and on top I also like using a flexible dock panel (or two or three). Cairo dock is a fantastic package in case that you would like to have a little bit of Mac OS X flavour on your Linux desktop, and adding a launcher for Visual Studio Code is very simply to do. Add Visual Studio Code to a dock panel like cairo dock or similar First, run Visual Studio Code using one of the previously described methods. Next, after the application runs and an icon of code appears in the dock panel right-click the icon, then select the sub-menu entry \"Make it a launcher\" from the \"code\" context menu entry and you're done. That's actually similar to pinning an application to the taskbar in Windows 7, Windows 8 or Windows 10. Close the text editor and your new launcher will still remain in the dock panel. Resume on installing Visual Studio Code Without any question it is fantastic to have an identical text editor for all three major operating system. But Linux users are currently confronted with some lack of comfort compared to their Windows and Mac OS X friends. Although there are several and in my opinion easy ways to increase the user experience in using Visual Studio Code under Linux I'm a bit concerned whether Microsoft is keeping it on par to the other systems. Right now, installation takes some manual steps, there are essential parts missing in order to provide an excellent first contact and other editor features like automatic updates aren't yet available for the Linux variation compared to Windows and Mac OS X. Bearing in mind that the product has been launched back in April/May this year only and we are currently on version 0.5.0, I am very interested in the future development. The documentation online has some neat features for you, and the team at Microsoft has an open ear to the feedback and wishes given on their UserVoice website, too. That's all for the installation part of Visual Studio Code. Please leave your comments as well as tips & tricks for me. Happy coding! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'install-visual-studio-code-on-linux-ubuntu.html'; this.page.identifier = '9271ed1c97_idinstall-visual-studio-code-on-linux-ubuntu'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "integrate-google-adsense-ghost.html": {
    "href": "integrate-google-adsense-ghost.html",
    "title": "Integrate Google AdSense into your Ghost - Get Blogged by JoKi",
    "summary": "Integrate Google AdSense into your Ghost Copy Markdown View Markdown GitHub Markdown Source Integrate Google AdSense into your Ghost Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'integrate-google-adsense-ghost'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'integrate-google-adsense-ghost'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Integrate Google AdSense into your Ghost') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-09-25 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); A little bit of extra pocket money at the beginning can with some effort turn into a solid passive income. Google AdSense is one of the many platforms to monetize your site. Turn your passion into profit. AdSense is a free, simple way to make money online by placing ads on your website. In Migration to Ghost I wrote about how I migrated from my previous blogging software to Ghost but I didn't mention about the Google AdSense integration. Thanks to a brief chat on Facebook I was reminded about that and the following paragraphs describe the changes I applied. Decision on where to place AdSense Using advertisment on your Ghost blog site can help you to earn money online. But there are some aspects to take into consideration depending on your personal interest, the nature of your website and the expected audience. Showing adverts on your site might slow down loading performance Should the adverts shown on your site match with your content written? Where and how many adverts would you like to display on your site? What are the odds that too many ads could possible scare away your readers? Are you offering other services than reading content on your site? etc. My personal choice is that I wanted to display a single advert at the end of an article. Therefore, you won't find ads in any other location of this site (as of writing). Referring back to the FB conversation, here's the actual initiator of this article: Any specific config you did for google ads? Well, yes. This blog is mainly based on the default Ghost theme \"Casper\" and I had to add the necessary code and HTML fragments into my modified theme. Creating a partial view Given that Ghost themes are based on Handlebars and their flexible way of working with code fragments I created a new view in the partials folder called ads-banner.hbs. The JavaScript code has been generated by the AdSense dashboard and I simply copied it into a <section> element as part of my partial view: <section class=\"ads-banner\" style=\"justify-content:space-between;margin:0 auto;padding:1vw 0 2vw;\"> <script async src=\"//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js\"></script> <!-- Responsive - Tech --> <ins class=\"adsbygoogle\" style=\"display:block\" data-ad-client=\"ca-pub-<your-publisher-id-here>\" data-ad-slot=\"<your-ad-unit-id-here>\" data-ad-format=\"auto\"></ins> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script> </section> As per Google AdSense \"This ad unit can automatically adapt its size to the space available on the page.\" For further reference check out Google's Get started with ad code documentation online. Having a partial view gives a better flexibility using that snippet in various ways compared to just adding it hard-coded somewhere into the template. Referencing the partial view As mentioned, I decided to show adverts only after the content of an article but decided to show it before the Disqus comments. Articles or better said Stories in Ghost are displayed using the post.hbs view. {{/author}} </footer> {{> \"ads-banner\"}} {{!-- If you use Disqus comments, just uncomment this block. The only thing you need to change is \"test-apkdzgmqhj\" - which should be replaced with your own Disqus site-id. --}} <section class=\"post-full-comments\"> <div id=\"disqus_thread\"></div> In the code above I added a Partial Expression into the post view in order to embed the partial view created previously. Of course, you can decide to add as many partial expression in your posts template and other locations of your site as you like to. For more information about Handlebars used in Ghost I recommend that you dig deeper into the Themes documentation of Ghost. Restart Ghost service According to the Ghost theme structure you will need to restart Ghost each time you add or remove a file from the theme directory for it to be recognised and used. This is done on the command line using the ghost-cli command: $ ghost restart I case you're running multiple instances of Ghost on the same server it might be better to explicitly stop that particular Ghost instance and start it again instead of restarting all instances, like so: $ ghost stop [name] $ ghost start Using AdSense and Analytics A combination of both Google AdSense and Google Analytics is highly recommended as it helps you to get more details on the demographics and behaviour of your site visitors. This should also help you to provide more specialised and targeted campaigns on your site by eventually excluding certain topics not matching your actual content. Keeping an eye on the bounce rates shall give you an indicator whether visitors actually navigate around your site or just interested into a single article. My final recommendation would be: 'Less is More'. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'integrate-google-adsense-ghost.html'; this.page.identifier = '9271ed1c97_idintegrate-google-adsense-ghost'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "international-blogging.html": {
    "href": "international-blogging.html",
    "title": "International blogging - Get Blogged by JoKi",
    "summary": "International blogging Copy Markdown View Markdown GitHub Markdown Source International blogging Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'international-blogging'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'international-blogging'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'International blogging') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-01 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hi, so this is my first entry. Joining the Foxite community is hopefully a lot of fun and even more opportunity to learn more about Visual FoxPro and software engineering. So, maybe I should start with a short introduction to myself. My name is Jochen Kirstätter - aka JoKi - and I'm living near Kaiserslautern, Germany, Europe, Earth. I started developing software back in 1995. At that time I programmed dynamic web sites using HTML (UI), Perl and later PHP (business logic) and MySQL (database) - a natural three-tier environment. During spring 1999 I joined Kheops GmbH, a small company in Kaiserslautern and got in contact with Visual FoxPro 6 SP 5. Wow, amazing! And at that moment I did a lot of mistakes but during my first attendance at 1999s Devcon Germany I got the final idea and returned to multi-tier development. Puuh, VFP provides so much possibilities and I got lost in spaghetti code. But right after this conference I was delighted and got a way back to tier development. Thanks for that. In 2002 I got the chance to speak about Active FoxPro Pages (vendor session) at the German Devcon. OMG, I was so nervous and... it was a bad experience after all. But hey, it was also a chance to improve my capabilities as speaker and one year later it was really fun. In 2003 I did the sessions for Whil Hentzen (he got ill...) about VFP/Linux and I guess it was okay. So, still a year later, I had four sessions - two as regional director of the German FoxPro User Group and two about Active FoxPro Pages - and yup, finally I got the idea. I'm currently working on the sessions for this years conference later in November. At the moment I'm preparing three to four sessions. All about different aspects in VFP and development in general. Cross fingers... \uD83D\uDE09 Meanwhile I'm regional directory of the German FoxPro User Group in Speyer - the city with the dome... We meet monthly and have a really good time talking about VFP, SQL Server and sometimes about .NET framework. During 2003 Microsoft Germany contacted me to join their community program named CLIP. That was because of my FAQ website abou Active FoxPro Pages and VFP in general. And so the whole community related stuff exploded to the maximum and it's really a lot of fun. At the end of 2004 I was contacted by the European INETA communty - thanks to Rainer Becker who registered all dFPUG usergroups. And since this year I'm as well active in SQL PASS community to chat about Microsoft SQL Server - so, any technology I use at my daily work at Kheops is compained by a community activity - maybe I'm addicted to this whole stuff... \uD83D\uDE09 really dunno, but I have a lot of fun doing this. Additional to my daily job at Kheops and ProLib I develop small tools and other stuff - hopefully useful to anyone else than me. There are several - HTML Plugin for Active FoxPro Pages, VFP and COM interface, AfpWiki and Active FoxPro Pages FAQ. I hope to write some lines about those during next months... so, wait and see. Okidoki, so this is my international hello to the VFP community. Have fun, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'international-blogging.html'; this.page.identifier = '9271ed1c97_idinternational-blogging'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "internetcaches-unglaublich.html": {
    "href": "internetcaches-unglaublich.html",
    "title": "Internetcaches - unglaublich! - Get Blogged by JoKi",
    "summary": "Internetcaches - unglaublich! Copy Markdown View Markdown GitHub Markdown Source Internetcaches - unglaublich! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'internetcaches-unglaublich'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'internetcaches-unglaublich'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Internetcaches - unglaublich!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-18 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wow, ich bin mal wieder positiv sprachlos! Eigentlich hatte ich bereits vor längerer Zeit die Suche nach weiteren Einträgen meines Blogs eingestellt, da nichts mehr aufzutreiben war. Aber heute ergab es sich mal wieder aus einer Laune heraus, dass ich nach etwas Bestimmten suchte und... Äh, was macht denn ein Blogeintrag von mir in der Trefferliste? Konkret ging's um das Thema HTML Metatags und Verbesserung des Rankings. Nun, cool, mal sehen, ob ich den wieder in der Datenbank habe... Nö. Noch besser! Ein wenig angekratzt, wieso denn der Beitrag jetzt erst im Cache von Google auftaucht, wurde ich etwas mutiger und habe mal in der Blogdatenbank nachgeschaut, zu welchen Einträgen ich zwar eine Überschrift aber keinen Beitrag habe - Okay, fein, damit haben wir ja schon mal eine Schlagwortliste für das Interview mit Google. Bewaffnet mit einer kleinen, aber feinen Spezialität der Google-Engine - dem Suchparameter site: - ging's auch gleich ab in die Tiefen des Caches... und nach einigen Stunden wieder überglücklich heraus. Leute, das war der beste virtuelle Tauchgang meines Lebens. Ich hab' locker 30(!! alte Beiträge komplett wieder gefunden. Darunter auch die informativen Einträge von der Devcon Prag 2005 - mein Favorit ist die Heimfahrt - und weitere Schmankerl, die ich schon längst abgeschrieben hatte. Der Servercrash ist damit inzwischen rein datentechnisch betrachtet überstanden. Neben der Tatsache, dass ich wieder alle Artikel der AFP FAQ in allen Sprachen besitze, bin ich natürlich wesentlich glücklicher darüber, dass auch nun meinen persönlichen Artikel des Blogs wieder online zur Verfügung stehen. Und ja, man wird schlauer, denn inzwischen mache ich jede Woche ein Fullbackup der eingesetzten Datenbanken meiner Websites. Dabei werden dann auch die optischen Templates gezogen... Leute, es macht wirklich Spass in den Beiträgen aus 2005 zu lesen. Unglaublich viel Spass. Und einmal mehr gilt mein Dank den Machern und Betreibern von Google - Leute, eure Infrastruktur ist der echte Hammer im Internet - Danke! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'internetcaches-unglaublich.html'; this.page.identifier = '9271ed1c97_idinternetcaches-unglaublich'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "intranet-explorer.html": {
    "href": "intranet-explorer.html",
    "title": "Intranet Explorer - Get Blogged by JoKi",
    "summary": "Intranet Explorer Copy Markdown View Markdown GitHub Markdown Source Intranet Explorer Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'intranet-explorer'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'intranet-explorer'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Intranet Explorer') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-12 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ja, das erscheint mir aktuell als wesentlich passenderen Namen für den Microsoft IE. Wieso komme ich darauf? Ausgehend von einer laufenden Unterhaltung mit dem Eisbären ergab sich die Idee nahezu als Konsequenz und mit einigen Überlegungen gefällt mir der Umstand sogar noch besser. Hier mal der Einstieg für euch: *12.10.2005 14:39 Delfin@Work: Öhem... cool... Schon mal ins Infosystem geschaut. Ich hab' irgendwie den Rahmen bei der Combobox entsorgt... 12.10.2005 14:44 Der Eisbär @ Work: Hm 12.10.2005 14:44 Der Eisbär @ Work: Also falls Du rausfindest, wie, sags mir ^^ 12.10.2005 14:45 Delfin@Work: Yup 12.10.2005 14:45 Der Eisbär @ Work: Mal so die ganz doofe Vermutung, dass es ein simples border = 0 ist, ist es wohl nicht? 12.10.2005 14:45 Delfin@Work: Könnte sein, aber hatte ich mal in der Vergangenheit probiert... 12.10.2005 14:46 Delfin@Work: Hm, könnte aber auch am Firefox 1.5 liegen. Denn im IE ist der Rahmen noch da. 12.10.2005 14:47 Der Eisbär @ Work: Frickelfox 1.5 is raus? 12.10.2005 14:47 Der Eisbär @ Work: Ah okay, Beta ^^ 12.10.2005 14:47 Der Eisbär @ Work: Hm, auf den bin ich ja auch mal sehr gespannt 12.10.2005 14:48 Delfin@Work: Wie kommst du auf Frickelfox? 12.10.2005 14:48 Der Eisbär @ Work: lach Das war net so ernst gemeint 12.10.2005 14:48 Delfin@Work: Ich vermute, dass es mit CSS3 zu tun hat. 12.10.2005 14:49 Der Eisbär @ Work: Hmmm .... irgendwie müsst ich mein Wissen in dem Bereich auch mal wieder polieren 12.10.2005 14:49 Delfin@Work: Hm, da fällt mir gerade ein, dss MS seinen IE umbenennen sollte.. 12.10.2005 14:49 Der Eisbär @ Work: In? 12.10.2005 14:50 Delfin@Work: In \"Intranet Explorer\", denn da ist er am besten aufgehoben und die Leistungen sind überzeugend. In einem kontrollierten Umfeld ist das Teil geeigneter. 12.10.2005 14:50 Der Eisbär @ Work: Hm, da ist durchaus was dran 12.10.2005 14:50 Der Eisbär @ Work: Aber hey, wenn das dann bedeutet, dass statt dem IE der Windowsstandardbrowser der MSN Explorer wird, dann gute Nacht 12.10.2005 14:51 Delfin@Work: Tjoa, dann erst recht nur Intranet... 12.10.2005 14:51 Der Eisbär @ Work: Zielgruppe \"Frauen und Kinder\", wenn ich so was schon höre * Und wenn man sich einige webbasierte Anwendungen sowie webbasierte Software betrachtet, dann stellt man in den meisten Fällen garantiert zwei Dinge fest: Es sind meistens Intranet-Anwendungen und eher selten im offenen Dschungel des Internets anzutreffen Die Implementierung ist optimiert auf den Microsoft Browser Über den ersten Punkt lässt sich sicherlich viel diskutieren; jedoch sehe ich beim zweiten Punkt ein klassisches Henne-Ei-Problem. Denn viele Hersteller von webbasierten Lösungen nehmen den IE als Marktführer für die Windowsplattform als das Maß aller Dinge für ihre Entwicklung und vernachlässigen damit (un-)bewusst andere Browser und andere Betriebssysteme. Schade eigentlich, da sich hier sicherlich noch einige Lizenzen für Clients machen liessen, und es im Widerspruch zur Idee der vernetzten Informationsanbietung steht. Also, gehen wir davon aus, dass wir in einer kontrollierten, gesicherten Arbeitsumgebung aktiv sein können. Und jetzt, kann ich mir auch wieder den IE als geeignetes Werkzeug zur Anwendung der webbasierten Software vorstellen. Daher auch die Idee für die Namensänderung Intranet Explorer. Zusätzlich müsste eine entsprechende Netzwerk-Kontrollinstanz sicherstellen, dass keine externen Requests (also dem bösen Internet) ausgehend vom IE mittels Proxies und Firewalls zugelassen wird. Hier sehe ich ein besonderes Tätigkeitsfeld für den Systemadministrator. \uD83D\uDE0E Und wir haben eine klare Aufgabentrennung: Intranet kann der IE übernehmen, während im Internet sorgenfrei(er) mit den Gecko-basierten Browsern navigiert wird. Wie seht ihr die Situation? Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'intranet-explorer.html'; this.page.identifier = '9271ed1c97_idintranet-explorer'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "intro-to-mscc.html": {
    "href": "intro-to-mscc.html",
    "title": "Introduction to Mauritius Software Craftsmanship Community - Get Blogged by JoKi",
    "summary": "Introduction to Mauritius Software Craftsmanship Community Copy Markdown View Markdown GitHub Markdown Source Introduction to Mauritius Software Craftsmanship Community Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'intro-to-mscc'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'intro-to-mscc'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Introduction to Mauritius Software Craftsmanship Community') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-03-01 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Whether you are working in the field of Internet Technology (IT) since a long time or you just started your studies in Computer Science recently, at some point in time you might have the urge to meet and exchange with other like-minded people. Communities and user groups are surely the way to go and most likely there is an active user group already not far from you. Although user groups are not new concepts it was a bit surprising for me that discover the lack of an active community here in Mauritius - I mean active ones. According to some search queries on Google you can easily find relicts of previously existing user groups; but unfortunately most of them aren't active anymore. While having a closer look at the search results and with the experience I gained during earlier years as a member of various communities and even being responsible to organise monthly meetings for more than two years, I finally decided that Mauritius should have a vital and thriving IT community again. No offense towards the other existing ones but as said, they were either long forgotten and abandonded or acted very dormant in some remote corner of the island. Anyway, back in May 2013 I started to organise myself a little bit and officially announced the existence of the Mauritius Software Craftsmanship Community, or short: MSCC, on various social media channels. What is the MSCC? Let me quote the official \"About Us\" statement from our official site: \"This is a community for those who care and are proud of what they do. For those developers, regardless how experienced they are, who want to improve and master their craft. This is a community for those who believe that being average is just not good enough.\" A simply mission statement. The MSCC is technology-agnostic community and spans an umbrella over any kind of technology. Simply because you can't ignore other technologies anymore in a connected IT world as we have. A front-end developer for iOS applications should have the chance to connect with a Python back-end coder and eventually with a DBA for MySQL or PostgreSQL and exchange their experience. Furthermore, I'm a huge fan of cross-platform development, and it is very pleasant to have pure Web developers - with all that HTML5, CSS3, JavaScript and JS libraries stuff - and passionate C# or Java coders at the same table. This diversity of knowledge can assist and boost your personal situation. And last but not least, there are projects and open positions 'flying' around... People might like to hear others opinion about an employer or get new impulses on how to tackle down an issue at their workspace, etc. This is about community. And that's how I see the MSCC in general - free of any limitations be it by programming language or technology. Having the chance to exchange experience and to discuss certain aspects of technology saves you time and money, and it's a pleasure to enjoy. Compared to dusty books and remote online resources. It's about humans! Elements of a successful community Within less than 1.5 years the number of registered craftsmen in our community reached 200+ members. The MSCC is now the largest IT community in Mauritius, maybe even in the region of Indian Ocean but there are have been a couple of techniques to make this happen. Following I would like to describe some key indicators that helped to thrive this user group. Of course, the list is not complete and they are surely more aspects in play to achieve this goal; at least it might be some starting point for others. Regular dates, easy accessible locations (and stick to it) Trust me, especially at the beginning it might be frustrating and discouraging. The first two or three meetups, I was sitting on my own at a local bistro and couldn't talk to anyone else about my current struggles in coding or about discussing some vital ideas with someone else. Be persistent! It takes time that other people in your area take note and they also have to see whether they can arrange their schedule to come and visit. The MSCC has currently two schedules: The weekly Code & Coffee session every Wednesday morning The monthly topic during the last Saturday of a month Of course, you don't to have to stick to this one but at least focus on regular, easy to remember dates, ie. third Monday evening, or similar. Organise topic-specific events and give away SWAG During the weekly meetings we usually exchange about recent news in the IT scene, talk about various ideas and technologies and sometimes put our heads together to work out an issue a craftsmen brought on that day. Personally, I'm enjoying the time out of office, and for a good number of times I got great hints from others and was able to improve my daily routine and productivity. You would be surprised about how much a short personal chat with someone else could help you to get better on your job. Our monthly meetups are usually centered around a specific topic, and most of the time it is other community members that share there experience in a certain field with all the other attendees. Usually, we choose two sessions of approximately 45 to 60 minutes and then have a nice Q & A session afterwa Active contribution to social media channels Creating awareness of the community and keeping an open communication channels for announcements and exchange between members is very important. The MSCC is present on all major social media platforms like Twitter, Facebook, Google+ and LinkedIn. In case that this might take too much of time, there are multiple tools on the market that allow simultaneous posting to all of those platforms. Your user group members have different taste and preferences, so don't miss the chance to be present for all of them. Engage with members and ask for assistance An IT community is about latest news in technology, IT-related promotions and other cool things that are worth sharing and discussing. Again be persistent and post interesting information through the social media platforms, retweet, share postings and be an attractive source for your members. Even though there are endless numbers of news publications don't be afraid to collect some good ones and redistribute them through the user group. In general, it might be also interesting to engage some selected group members to assist you. Especially, about publication of news and promotional offers. Engage, encourage, share the load and have great fun together. Also, in terms of work related scenarios it's great to have some vital communication among user group attendees. The number of project offers, requests for some freelancing work and consulting activities has constantly increased between our members. The networking sessions during our monthly meetups are highly appreciated by the MSCC craftsmen as they offer the chance to get in touch with skilled people. Someone might be looking for a job while another attendee is desperately looking for talent to fill an open position. Work together with other communities Do not distinguish or isolate yourself from other IT communities. No, quite the opposite: Embrace their activities, learn from each and other maybe even organise common events. Despite the short period of existence of the Mauritius Software Craftsmanship Community there are positive connections to several other user groups here in Mauritius. Most interestingly, the MSCC acted somehow as a catalyst or igniter for them. Back in 2013 the local Linux user group - LUGM - was kind of dormant with very low profile and irregular get-togethers. A bit inspired by the our communities activities the LUGM experienced a wave of new motivation. There have been several Linuxfest events since then, and future events are planned together. Another example is the newly founded chapter of Startup Grind in Mauritius. The founder is one of the MSCC craftsmen with great ideas and ambitions in the field of entrepreneurship. You see, communication and networking are essential to keep an IT community like the MSCC prospering. Your future looks bright Running and participating in a user group or any kind of community usually provides quite a number of advantages for anyone. On the one side it is very joyful for me to organise appointments and get in touch with people that might be interested to present a little demo of their projects or their recent problems they had to tackle down, and on the other side there are lots of companies that have various support programs or sponsorship especially tailored for user groups. Don't be shy! Contact them and ask for gimmicks that you could hand out in small contests or raffles during one of the upcoming meetings. Usually those companies provide all kind of goodies, books free of charge, or sometimes even licenses for communities. Meeting other software developers or IT guys also opens up your point of view on the local market and there might be interesting projects or job offers available, too. A community like the Mauritius Software Craftsmanship Community is great for freelancers, self-employed, students and of course employees. Meetings will be organised on a regular basis, and I'm open to all kind of suggestions from you. Please leave a comment and join the conversations on social networks. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'intro-to-mscc.html'; this.page.identifier = '9271ed1c97_idintro-to-mscc'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "introducing-microsoft-sql-server-2005-for-developers.html": {
    "href": "introducing-microsoft-sql-server-2005-for-developers.html",
    "title": "Introducing Microsoft SQL Server 2005 For Developers - Get Blogged by JoKi",
    "summary": "Introducing Microsoft SQL Server 2005 For Developers Copy Markdown View Markdown GitHub Markdown Source Introducing Microsoft SQL Server 2005 For Developers Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'introducing-microsoft-sql-server-2005-for-developers'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'introducing-microsoft-sql-server-2005-for-developers'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Introducing Microsoft SQL Server 2005 For Developers') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-14 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Rezension: Introducing Microsoft SQL Server 2005 For Developers Von Peter DeBetta Microsoft wird im Laufe des Jahres 2005 das Nachfolgeprodukt zu SQL Server 2000 veröffentlichen. In Introducing Microsoft SQL Server 2005 For Developers betrachtet Peter DeBetta das Produkt aus der Sichtweise eines Anwendungsentwicklers, und liefert anschaulich einen Überblick samt Beispielen zu den Verbesserungen und den neuen Technologien. Als eines der ersten Bücher zu Microsoft SQL Server 2005, auch unter dem Codenamen â€œYukonâ€\u009D bekannt, richtet sich das Buch primär an Entwickler, die bereits mit dem SQL Server 2000 arbeiten, und sich über die aufregenden Änderungen frühzeitig informieren möchten. Das Buch eignet sich ebenfalls für Datenbankadministratoren mit programmiertechnischen Ambitionen. Sie erhalten Einblicke in die Verbesserungen und die neuen Technologien aus der Sichtweise eines Entwicklers. In den einzelnen Kapiteln: ? SQL Server Management Studio and Transact-SQL ? But Wait...More Transact-SQL ? XML-The Real Deal ? Microsoft .NET Framework Integration ? User-Defined DataTypes ? Client-Side ADO.NET ? Security ? Data Transformation Services ? SQL Server Management Objects ? Native HTTP SOAP ? Notification Services ? SQL Server Service Broker ? Reporting Services vermittelt Ihnen Peter DeBetta mit leicht verständlichen und nachvollziehbaren Beispielen den Einsatzzweck und die grundsätzliche Funktionsweise der Technologien. Das Buch versteht sich als eine Einführung zu den Themen, dennoch geben die einzelnen Kapitel den Inhalt in ausreichender Wissentiefe wider, so dass Sie mit den gewonnenen Kenntnissen ad hoc in Ihren Anwendungen und Datenbanken loslegen könnten. Der Schwerpunkt der Neuerungen beim Microsoft SQL Server 2005 liegt weiterhin auf der Nutzung und Programmierung von Transact-SQL. Techniken wie etwa die Integration des .NET Frameworks erweitern die Möglichkeiten des Servers. Die ersten Kapitel bieten Ihnen neben ausreichend Quellcode in Transact-SQL und C# ebenfalls eine Art Entscheidungsleitfaden durch die kritische Analyse und Einschätzung der jeweiligen Lösung. In den weiteren Abschnitten erhalten Sie Einblicke in die verbesserten und neu hinzugefügten Schnittstellen des SQL Server 2005. Besonders die programmatischen Schnittstellen bieten die größten Reize für einen Entwickler und hier hat Microsoft einiges zu bieten. Die Data Transformation Services wurden vollständig neu geschrieben und mit einer umfangreichen Programmschnittstelle versehen. Die SQL Distributed Management Objects (SQL-DMO) und die XML-Fähigkeiten des SQL Server 2000 liegen nun mit mehr Funktionalität in Form der SQL Server Management Objects (SMO) und HTTP Endpoints vor. Die hinteren Kapitel zu den Themen Notification Services, dem SQL Server Service Broker und den Reporting Services können leider nur die Spitze des Eisberges der einzelnen Technologie anbieten. Das jeweilige Teilgebiet ist schlichtweg zu umfangreich für eine Vorstellung. Fairerweise verweist der Autor hier ausdrücklich auf weiterführende Literatur, bietet dennoch ausreichend Lesestoff und Beispiele für die ersten Schritte. Insgesamt liefert dieses Buch â€“ gemäß seinem Titel â€“ eine umfangreiche und leicht verständliche Vorstellung der Verbesserungen und der neuen Technologien des Microsoft SQL Server 2005. Neben der lockeren Schreibweise und den verständlichen Beispielen wartet das Buch mit einem kleinen Bonushäppchen auf: Im Anhang steht Ihnen ein Kapitel aus dem Buch The Microsoft Plaform Ahead von David S. Platt zur Verfügung. ISBN: 0-7356-1962-X Umfang: 272 Seiten Sprache: Englisch Verfügbare Buchformate: Gedruckt Preis: US $29,99 (EUR 25,50) Druckdatum: Oktober 2004 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'introducing-microsoft-sql-server-2005-for-developers.html'; this.page.identifier = '9271ed1c97_idintroducing-microsoft-sql-server-2005-for-developers'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "irgendwas-habe-ich-vergessen----.html": {
    "href": "irgendwas-habe-ich-vergessen----.html",
    "title": "Irgendwas habe ich vergessen... ? - Get Blogged by JoKi",
    "summary": "Irgendwas habe ich vergessen... ? Copy Markdown View Markdown GitHub Markdown Source Irgendwas habe ich vergessen... ? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'irgendwas-habe-ich-vergessen----'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'irgendwas-habe-ich-vergessen----'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Irgendwas habe ich vergessen... ?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2007-01-25 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich bin mir wirklich sehr sicher, dass ich irgendetwas vergessen habe. Ich komm' zwar aktuell nicht drauf, aber da war 100% noch was gewesen. Hmm, mal drüber nachgrübeln und weiterschreiben; vielleicht fällt es mir ja wieder ein. 2007 - neues Jahr, neues Glück, neue Aufgaben und Ziele Der Jahresabschluß 2006 ist sowohl kalendarisch wie auch gedanklich inzwischen abgeschlossen und das neue Jahr hat begonnen. Zumindest bei mir vergeht der Januar immer wie im Fluge... nunja, diesmal sogar im tatsächlichen Sinn des Wortes, aber dazu gleich mehr. Ich hoffe, dass es euch der Jahreswechsel ähnlich gut ergangen ist. 2007 - neue Kategorie angelegt Ich gehe davon aus, dass es bisher noch keinem Leser richtig aufgefallen ist, daher möchte ich es hier betonen. Seit einigen Tagen habe ich eine neue Artikelkategorie - Globetrotter - angelegt. Darin werde ich ein wenig von meinen Reiseerlebnissen berichten. Dies hatte ich ja bereits in der Vergangenheit schon ab und zu mal getan, aber ich denke, dass es sich künftig rentieren wird, diese Beiträge eigenständig zu führen. Auch hierzu gleich mehr... 2007 - neue Aufgaben Meine aktuellen Projektaufgaben auf der Arbeit gehen nach etlichen Monaten intensiver Beschäftigung so langsam dem Ende entgegen. Zwar stehen wir derzeit noch nicht in weiteren Verhandlungen, aber wir wollen mal sehen, was 2007 im Bereich der Projektleitung und Software-Entwicklung für mich bringen wird. Auch hierzu werde ich im Laufe des Jahres hoffentlich ein paar Infos geben können... Achja, da war doch was... Genau, jetzt ist es mir wieder eingefallen. Dieses Blog! \uD83E\uDD2A Sorry, dass ich fast 2 Monate keinen neuen Beitrag mehr geschrieben habe und auch dass dieses Blog einige Wochen offline schien. Ich möchte hierzu keine weiteren Worte verlieren. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'irgendwas-habe-ich-vergessen----.html'; this.page.identifier = '9271ed1c97_idirgendwas-habe-ich-vergessen----'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "ja-ist-denn-schon-weihnachten.html": {
    "href": "ja-ist-denn-schon-weihnachten.html",
    "title": "Ja, ist denn schon Weihnachten? - Get Blogged by JoKi",
    "summary": "Ja, ist denn schon Weihnachten? Copy Markdown View Markdown GitHub Markdown Source Ja, ist denn schon Weihnachten? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ja-ist-denn-schon-weihnachten'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'ja-ist-denn-schon-weihnachten'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ja, ist denn schon Weihnachten?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-18 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); So ging es mir als ich gestern Abend nach Hause kam und ein freundlicher Zettel meiner Nachbarn an der Tür klebte: \"Wir haben ein Paket für dich angenommen, Gruß.\" - Hm, Paket? Ich hab' doch gar nichts bestellt. Naja, okay, kann noch ein bisschen warten... Zuerst wollte ich mich noch ein wenig Entspannen und bin 4.25 Kilometer über Feldwege bei 2° Celsius durch die Gegend gejoggt. Es ist herrlich bei dem Wetter unterwegs zu sein. Absolut empfehlenswert. Auf dem Rückweg dann noch schnell bei Nachbars vorbeigeschaut und das Paket abgeholt. Hm, der Herr Nachbar sah mich etwas verduzt an, aber okay, so sieht man eben aus, wenn man gerade joggen war... Egal, das Paket kam von Microsoft, genauer gesagt von der INETA - Yeah! Cool, auf alle Fälle rechtzeitig zu unserem nächsten VFP Stammtisch in Speyer - der genaue Termin ist noch offen... Die Kiste mit den Weihnachtspräsenten füllt sich so langsam... Mein Dank gilt allen Sponsoren für die Möglichkeit, dass wir zu Weihnachten eine kleine Tombola veranstalten können. Ich bin mal gespannt, was sich noch organisieren lässt. Hehehe, für einen Stammtisch kann man nie genug haben. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'ja-ist-denn-schon-weihnachten.html'; this.page.identifier = '9271ed1c97_idja-ist-denn-schon-weihnachten'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "javascript-macht-spass.html": {
    "href": "javascript-macht-spass.html",
    "title": "JavaScript macht Spass - Get Blogged by JoKi",
    "summary": "JavaScript macht Spass Copy Markdown View Markdown GitHub Markdown Source JavaScript macht Spass Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'javascript-macht-spass'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'javascript-macht-spass'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'JavaScript macht Spass') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-07-13 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Vielleicht kennt ihr auch das Problem... Man hat eine brauchbare GUI in HTML server-seitig mittels einer Skriptsprache wie die Active FoxPro Pages - https://www.afpages.de - zusammengesetzt und die Kommunikation zwischen Client und Server harmoniert auch sehr gut, aber irgendwie bleibt das Gefühl, daß noch etwas fehlt. Nun, so ging es mir auch bei der Programmierung einer externen Website... Es handelt sich um eine Überarbeitung, die noch im Gange ist. Aber die primären Lebensfunktionen der Site sind vorhanden, es gibt ausreichend Content, aber... wie schon gesagt, es fehlt was. Okay, die einzige Möglichkeit der Site mehr Leben und Interaktion mit dem Benutzer einzuhauchen besteht darin, daß man auf eine client-seitige Skriptsprache zurückgreift und darin kräftig programmiert. Als mögliche Kandidaten kommen max. 3 Kandidaten in Frage: JavaScript - der Platzhirsch JScript - die Sparversion von Microsoft VBScript - das Sicherheitsrisiko Irgendwie fällt alles unter den Begriff ECMAScript, aber nagelt mich nicht über das Was und Wie fest. Nun denn, die Entscheidung ist sehr eindeutig zu Gunsten JavaScript ausgefallen, da die anderen keine echte Alternative darstellen und extremst an das Document Object Modell (DOM) des Internet Explorer gebunden sind. JavaScript bietet aufgrund der Specs des W3C zudem alle Optionen für eine saubere, browserübergreifende Implementierung der gewünschten Funktionalität - und es läuft in allen Browsern... Ja, I know - Ausnahmen bestätigen die Regel und schimpfen sich Konqueror und Safari. Sorry, guys! Aber wieder zurück zum Geschehen und der Idee: Mehr Interaktion mit dem Benutzer. Nun im konkreten Fall ging es um eine Wiki-GUI, deren server-seitige Engine bereits sehr ausgereift ist. Nach einer kurzen Analyse ergibt sich das Szenario, daß jede Seite durch einen Begriff bzw. durch das WikiWord eindeutig auffindbar ist. Okay, kleiner Suchdialog und fertig. Sofern dieser omnipräsent ist... In diesem Fall ist aber nicht so. Also kam mir die Idee eines 'versteckten' Suchdialogs. Wir erinnern uns, das WikiWord ist entscheidend! Die aktuelle Implementierung in JavaScript gestattet es, durch Doppelklick auf das aktuelle WikiWord die GUI auf eine 'Suchmaske' umzuschalten, welche durch Bestätigen mit Enter ausgelöst oder durch Klick irgendwo hin wieder geschlossen wird. Einfach, aber praktisch, oder? Und Dank JavaScript funktioniert es in den meisten Browsern. \uD83D\uDE0E Achja, noch eine kleine Anmerkung, es ist äußerst hilfreich mit den Browser aus der Mozilla-Familie zu testen, da diese sowohl eine JavaScript-Konsole als auch einen DOM Inspector bereitstellen. Was IMHO wesentlich mehr Vorteile als ein stupider Fehlerhinweis vom IE bietet. Aber das sei jeder/m selbst überlassen. Keep on coding! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'javascript-macht-spass.html'; this.page.identifier = '9271ed1c97_idjavascript-macht-spass'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "jeder-sollte-ein-hamsterfell-besitzen.html": {
    "href": "jeder-sollte-ein-hamsterfell-besitzen.html",
    "title": "Jeder sollte ein Hamsterfell besitzen - Get Blogged by JoKi",
    "summary": "Jeder sollte ein Hamsterfell besitzen Copy Markdown View Markdown GitHub Markdown Source Jeder sollte ein Hamsterfell besitzen Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'jeder-sollte-ein-hamsterfell-besitzen'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'jeder-sollte-ein-hamsterfell-besitzen'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Jeder sollte ein Hamsterfell besitzen') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-04 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wie genial!! https://www.hamsterfelle.de/ Schaut euch diese coole Seite an, deren Shopangebot ist der absolute Hammer. Und im Gästebuch gibt's Spitzenkommentare dazu. Ist echt interessant, wie sich doch der Begriff Haustier im Laufe der Zeit wandelt... Nunja, dazu fällt mir derzeit nur folgendes ein: All animals are equal, but some animals are more equal than others. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'jeder-sollte-ein-hamsterfell-besitzen.html'; this.page.identifier = '9271ed1c97_idjeder-sollte-ein-hamsterfell-besitzen'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "john-onolan.html": {
    "href": "john-onolan.html",
    "title": "Campsite fire talk with John O'Nolan - Get Blogged by JoKi",
    "summary": "Campsite fire talk with John O'Nolan Copy Markdown View Markdown GitHub Markdown Source Campsite fire talk with John O'Nolan Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'john-onolan'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'john-onolan'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Campsite fire talk with John O'Nolan') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-11-13 - Filed under CommunityActivity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Good things take time. After more than a year I finally managed to sit down and write about the experience of welcoming John O'Nolan, founder of Ghost blogging platform, to one of our MSCC meetings. How did you manage that? Honestly, it wasn't me but one of our female craftsman, Humeira. She is a subscriber to John's YouTube channel and given his vlogging activity he mentioned that he would be En route to Mauritius soon. So, Humeira poked me over Twitter asking whether it could be interesting to meet with John or not. Given such a great opportunity you don't have to ask me: Yes, of course! Living on a remote, tropical island like Mauritius doesn't offer you too many options to meet with famous people from the rest of the world. Hence, we seize every chance to meet and to get to know someone's story. Dear reader, in case that you decide to come and visit our lovely island, get in touch with either me or any other MSCC craftsmen. Everybody is eager to meet and hear your story. Well, effectively I encouraged Humeira to check out whether John would be okay to join us for an evening, and assisted a little bit to arrange a get together at Flying Dodo. He agreed and the rest... please read on. Ghost - Happy Hour Q&A The event Ghost - Happy Hour Q&A with John O'Nolan was organised and announced on quite a short notice. And despite a low number of RSVPs more craftsmen than expected came that evening. If I counted properly we had 27 attendees in total. A little history With a slight delay John then started to introduce himself and his previous activities working on WordPress. His passion about blogging then led to the very successful crowdfunding campaign on Kickstarter - Ghost: Just a Blogging Platform. There is an informative interview about How John O'Nolan Grew His Publishing Platform to $750,000/year available on Indie Hackers. Ghost - a minimalist blog platform John explained the origin of the name Ghost: It is based on the known concept of ghostwriting. Having a versatile assistant to compose interesting articles. Okay, while you're still the author the idea is that the blogging platform turns into an enjoyable environment to work with. Ghost is a fully open source, hackable platform for building and running a modern online publication. Since the original Kickstarter campaign the product has evolved nicely. Apart from the initial web interface to administer the blog, Ghost provides a fully-featured desktop experience built on top of the Electron environment for Windows, macOS and Linux, and recently an Android app has been added. The iOS counter-part app seems to be in the making (as of writing). Have a look at the Ghost Native Apps available for free. Personally, I'm usually writing my content in Ghost Desktop only. Bringing journalism to the next level John was very interested in the local media scene and spoke about the Ghost for Journalism initiative. A journalism development program to help build new, exciting, and sustainable publications. During 2017 the not-for-profit organisation behind Ghost ran this grant program to encourage independent news coverage on the net; unfortunately I haven't seen anything similar for 2018 yet. Our mission is to create the best open source tools for independent journalists and writers across the world, and have a real impact on the future of online media. You'll find more insights about that development program in John's Announcing Ghost for Journalism article on his blog. Perhaps you send him a tweet in case you have some serious contender; even nowadays, you never know. Open Source Did you know that the whole Ghost blog universe is open source and available on GitHub: TryGhost? Yup, that's pretty awesome. And the developer community around Ghost is busy. There are frequent updates with great improvements and stabilisations. Getting into development of Ghost themes for example is very easy and straight forward. Themes are based on Handlebars and the documentation provides all information needed to create appealing and modern UIs for a blog running on Ghost. The environment to develop Ghost itself is a bit trickier but doable as soon as you are more familiar with the architecture and the various components. Get Blogged: Long overdue upgrade The months prior to this event I was procrastinating myself. On one hand I really wanted to write more content again like in previous years but on the other hand I have to admit that I grew tired of the cumbersome editor in Joomla. I don't know but it felt like torture even trying to write something. Although I already heard a little bit about Ghost from other craftsmen in MSCC I never had the urge to actually give it a shot. Now, knowing the story behind Ghost from John himself puts a complete different light on that whole blog thing. Perhaps I just needed that last drop of water to get going again. And John convinced me to try Ghost. But even then it took almost another half year to get going finally. With the official Announcing Ghost 1.0 I started to explore Ghost 1.0.2. And it was a breeze. Having several root servers myself I wanted to explore the possibilities of self-hosting the Ghost platform. Using their ghost-cli application gets your started instantly on a Linux machine. Open a terminal, create a new empty folder and run the install command of Ghost, like so: $ mkdir ghost $ cd !$ $ ghost install local The ghost-cli runs a couple of checks to see whether your system is compatible and warns you in case that you're not using the recommended platform. Feel free to continue; we want to experiment with and discover Ghost. Don't skip the Getting Started guide in the official documentation of Ghost. It is very detailed and resourceful. Oh, and feel free to read another article here on the blog in case that you are doing a Migration to Ghost from Joomla. Try it yourself Self-hosting as described in the previous paragraph is just one option. And not everyone has neither a Linux machine nor a root server to operate Ghost after all. Perhaps you might like to head over to Ghost and test their platform yourself. Try Ghost(Pro) free for 14 days, no obligation. John in Mauritius In case that you are interested to know more about Ghost and John, I recommend you to head over to John O'Nolan's website. There are a good number of gems available. Although it seems that John evolved into Vlogs compared to Blogs. Over there you will also find an article inclusive video link about John's experience in #TropicalValley - Mauritius: One of the most beautiful places in the world. It is quite hilarious actually. Enjoy! Talking about Vlogs... The whole conversation has been recorded. Kudos to the folks of LSL Digital. If you are interested to watch it, check out the MSCC channel on YouTube. Good night, John! Thank You! It was an amazing evening and I'm sure that our craftsmen appreciated your talk. I learned a lot from that camp-site fire talk with John and the elegance of Ghost motivated me to launch this website with a new look and fresh content. Meeting John O'Nolan was really fun! Thanks! Image credit: Nirvan Pagooah if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'john-onolan.html'; this.page.identifier = '9271ed1c97_idjohn-onolan'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "jubel--trubel--heiterkeit.html": {
    "href": "jubel--trubel--heiterkeit.html",
    "title": "Jubel, Trubel, Heiterkeit - Get Blogged by JoKi",
    "summary": "Jubel, Trubel, Heiterkeit Copy Markdown View Markdown GitHub Markdown Source Jubel, Trubel, Heiterkeit Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'jubel--trubel--heiterkeit'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'jubel--trubel--heiterkeit'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Jubel, Trubel, Heiterkeit') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2007-02-22 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Die närrischen Tage sind auch dieses Jahr unbeschadet an mir vorbeigezogen. Eigentlich geniesse ich die Faschingszeit schon, aber dieses Jahr ist nichts wie es bisher war und mir blieb letztendlich die Entscheidung, ob Party oder Zukunft - ich habe mich daher für Zukunft entschieden... denn Party kann man auch jederzeit selbst machen - da braucht's nicht extra Fasching für. Ja... Zukunft. Ich denke, dass inzwischen der richtige Zeitpunkt gekommen sein dürfte, um ein wenig mehr Informationen preis geben zu können. Irgendwann im Q3/Q4 2006 kam die Geschäftsführung auf mich zu, ob ich mir vorstellen könnte künftig im Ausland tätig zu sein. Prinzipiell natürlich ja... ich liebe die Ferne und bedingt durch Projektarbeit, etc. konnte ich 2006 auch wenig Auszeit geniessen. Und so entwickelte und manifestierte sich im Laufe der Monate der ganze Ansatz, dass wir im Ausland ein weiteres Büro öffnen werden. Meine Aufgabe dabei, umfasse primär den Kontakt nach Deutschland sowie die Koordination und Führung der neuen Entwicklungsteams - also prinzipiell der typische Offshore- bzw. Outsourcing-Fall. Konkret bedeutet dies für mich, dass ich statt wie bisher ein Projekt-Team aus sieben bis acht Entwicklern künftig eben mehrere Projekt-Teams mit insgesamt bis zu 20 Entwicklern leiten werde. Ich denke, dass meine langjährige Erfahrung als Gruppenführer in der Freiwilligen Feuerwehr hierbei sehr von Nutzen sein wird. Ja, und eben Ausland statt in Deutschland. Mein Chef wOOdy hat zwischenzeitlich dazu 'ne Info im Forum der dFPUG veröffentlicht. Dennoch hoffe ich, dass es auch noch eine offizielle Pressemitteilung über den neuen Sachverhalt geben wird. \uD83E\uDD2A .o( Boah, wieso redet der um den heißen Brei herum? ) Okay, okay... ich schreib' ja schon etwas zum Thema 'Ausland'. \uD83D\uDE01 So, nehmen wir mal den Globus und spannen einen Radius von etwa 9.500 Kilometer... was in etwa 11 Stunden Flugzeit entspricht. Dann orientieren wir uns ausgehend von Deutschland press nach Süden. Okay, damit landen wir dann irgendwo in Südafrika, aber da bin ich nicht... Wir folgen nun unserem Kreis nach Osten - vorbei an Madagaskar, dann vorbei an Le RÃ©union und dann... ja, dann landen wir auf einer kleinen, unabhängigen Insel. Es handelt sich hierbei um die Republik Mauritius mitten im Indischen Ozean. Die Koordinaten für Google Earth bzw. Virtual Earth werde ich euch demnächst noch bereitstellen. Achja, diesen Beitrag schreibe ich übrigens auf einer Veranda in Flic en Flac an der Westküste von Mauritius. Geschätzte Temperatur dürfte bei 31°C oder so liegen. Logisch, dass dabei ein Longdrink am Abend nicht fehlen darf. Der weitläufige Sandstrand ist zwar ca. sieben Gehminuten entfernt, aber wen interessiert's... Flic en Flac (FeF) ist übrigens eine der touristischen Hochburgen von Mauritius, neben Grand Baie und umliegend im Norden und einigen Orten an der Ostküste. Aber keinesfalls im gleichen Atemzug mit Mallorca zu nennen, erst recht nicht falls man den Ballermann im Sinn haben sollte. Nee nee, all in all, eine lockere, gepflegte Atmosphäre. Ich fühle mich richtig gut. Vor allem auch raus aus dem nasskalten Winter in Deutschland. Tsss, gestern um die Uhrzeit war ich noch in München, eigentlich Wahnsinn das Ganze, aber genial! Eventuell ist dem einen oder anderen Leser dieses Blogs aufgefallen, dass sich inzwischen auf der rechten Navigationsspalte eine neue Sektion namens 'Villa JoKi' samt Google Kalender befindet... falls nicht, angucken, sofort! \uD83E\uDD2A Dieser Kalender dient primär der Information für euch, wann ich zuhause bin und wie die Belegung in meiner neuen Unterkunft ist. Schliesslich hoffe ich, dass ich übers Jahr Besuch von Verwandten, Bekannten und Freunden sowie Kollegen erhalten werde. Ihr habt daher schon direkt den Überblick, ob und wann meine Location für euren Urlaub auf Mauritius geeignet ist. Aktuell ist noch alles frei und ich werde im März für einige Tage nicht zuhause sein, aber ich hoffe und gehe davon aus, dass sich im Laufe der Wochen Einträge finden werden. Nach wager Planung kündigen sich bereits für März und April die ersten Kollegen an, die mir dankenswerterweise mit den neuen Entwicklerteams unter die Arme greifen werden. So weit mal der Kalender... außerdem habe ich bei Google gleich noch ein Online-Photoalbum angelegt. Dort werde ich euch Bilder, und Bilder und ja noch mehr Bilder hier aus Mauritius zur Verfügung stellen. Das Limit liegt derzeit bei 250 MB oder so, aber bis dahin gehe ich davon aus, dass ich eine eigene Photogallerie auf diesem Server installiert haben dürfte. Also, ich hoffe, von euch zu hören und freue mich selbstverständlich über jeden Besuch. Schaut nach wie es bei euch passt, ob bei mir frei ist und schickt mir bitte 'ne kurze Info, so dass ich euch einplanen kann... Willkommen in der Villa JoKi - Welcome at Villa JoKi - Bienvenue Ã Villa JoKi So, nun aber genug getippert, habe noch 'ne Verabredung zum Abendessen... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'jubel--trubel--heiterkeit.html'; this.page.identifier = '9271ed1c97_idjubel--trubel--heiterkeit'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "kaum-lobt-man-die-deutsche-bahn.html": {
    "href": "kaum-lobt-man-die-deutsche-bahn.html",
    "title": "Kaum lobt man die Deutsche Bahn - Get Blogged by JoKi",
    "summary": "Kaum lobt man die Deutsche Bahn Copy Markdown View Markdown GitHub Markdown Source Kaum lobt man die Deutsche Bahn Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kaum-lobt-man-die-deutsche-bahn'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kaum-lobt-man-die-deutsche-bahn'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Kaum lobt man die Deutsche Bahn') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-16 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); ... wird man direkt eines Besseren belehrt. Aber starten wir den Tag am Morgen und berichten von den Details mit der DB später. Samstag - die Chance zum Ausschlafen Auch wenn ich noch im Hotel irgendwo südlich von Berlin erwache, so ist es doch Samstag und ich habe die Chance zum Ausschlafen. Die letzten Tage waren immer sehr früh und aktionsreich. Auch wenn ich nicht unbedingt viel Schlaf benötige, so kommen mir Tage wie dieser ab und zu doch sehr gelegen. Die letzten Tage, genauer die letzten drei, waren erfüllt mit einem Workshop zu Acodey - unserer Komponentenbibliothek. Bedingt durch die Mächtigkeit und der kundenspezifischen Anpassungen sind diese Art Workshops (un-)regelmäßig angesetzt. Die Vorteile liegen hierbei meines Erachtens klar auf der Hand: Der Kunde erhält die neuesten Informationen zu den Komponenten und deren Anwendung direkt vom Hersteller; wir haben die Chance besser auf die Anforderungen und Wünsche einzugehen und die Möglichkeit zur Erweiterung unserer Bibliothek. Ausschlafen ist hier übrigens im Sinne von 'kurz nach 7:00 Uhr' statt wie die sonstigen Tage um 6:30 Uhr zu verstehen. \uD83E\uDD2A - Das sind echte 'Todeszeiten' für einen nachtaktiven Entwickler wie mich. Aber es hat genau wie beim AFP-Workshop letzte Woche erneut sehr viel Spass und Freude bereitet, über unsere Produkte zu sprechen und Lösungen mit dem Kunden auszuarbeiten. Kein Wunder, waren es doch die gleichen Teilnehmer... Zusammen mit dem letzten Verbliebenen des Workshops waren wir die einzigen Gäste dieser Nacht überhaupt und entsprechend ruhig konnten wir uns das Frühstück zu Gemüte führen, und ein wenig über die Inhalte der letzten Tage resümieren. Und so ging die Zeit beinahe wie im Fluge um. Fahrkarte per Mobilfunk Coole Sache... 'Das Handy wird zur Fahrkarte' wie es die Deutsche Bahn betitelt. Nunja, der Begriff 'Handy' zeugt ja nicht sonderlich von Intelligenz, aber das Konzept ist sehr einfach, kundenfreundlich und stylish. Fahrkartenbuchung mit/ohne Reservierung sind für ICE und IC/EC ab 51 Kilometer Distanz möglich. Hm, okay, also dann doch noch traditionell am Schalter das Ticket für den Regionalexpress nach Berlin Hbf gekauft. Und ab geht's... Dank guter Funkverbindung und MMS-fähigen Gerät überbrücke ich die Zeit bis zur Landeshauptstadt mit der Suche nach einer geeigneten Verbindung in die Heimat. Hm, ja, etwa 19:00 Uhr zuhause ankommen klingt gut. Und zweimal Umsteigen ist auch akzeptabel, also schauen wir mal nach Reservierung und Buchen online. Alles ohne Notebook, direkt über den Browser des Mobilfunkgeräts. Dringring - sie haben eine MMS erhalten. Phantastisch, das ging sogar schneller als der Ticketautomat die Fahrkarte gedruckt und ausgespuckt hat. Die MMS selbst besteht aus zwei Teilen: Einem 2D-Barcode mit Zertifikat für die Kontrollautomaten der Zugbegleitung und den Ticketdaten im Klartext. Gut, Ticket vorliegend, dann wollen wir mal sehen, was bei der Prüfung im ICE passieren wird... \uD83D\uDE01 ICE wird 15 Jahre - und das Chaos bricht aus Nach einem kurzen Zwischenstopp am Berliner Hauptbahnhof geht's pünktlich im ICE los. Sitzplatzreservierung hat leider nicht ganz geklappt, aber das Sven-Prinzip ist aktiv und ich finde ein 6er-Abteil mit nur drei Reservierungen. Hehehe, mein Sitzplatz, komme wer wolle... Und die weiteren Fahrtgäste lassen nicht auf sich warten. Am Ende sind wir ausgebucht. Ein rustikales Ehepartner auf dem Weg nach Bad Sobernheim und vier Jungspunde - übrigens in Balance (3:3) - wenn ich mich da mal einbeziehen darf. Die Fahrt verläuft gut. Ja, bis kurz vor Braunschweig... wir werden langsamer und 'dürfen' dann bedingt durch Notarzteinsatz samt Personenschaden mitten in der Prärie stehen bleiben. Nach einiger Zeit läuft ein sichtlich entrüsteter und laut zetender Zugführer an uns vorbei. Tja, bleibt aktuell nur der Weg zurück über Magdeburg. Achja, und inzwischen gab's die erste Fahrscheinprüfung. 'Och, nicht die neuen Dinger.' - g - tjoa, Service am Kunden. Genau wie beschrieben, scannt der Zugbegleiter das Mobilfunkgerät und prüft mein Authentifizierung. Soweit alles in Ordnung. Das war ja einfach... Die Zeit verrinnt... ... im wahrsten Sinne der Worte. Habe ich schon erwähnt, dass wir immer noch stehen? Okay, nach mehr als einer Stunde informiert uns das Zugpersonal, dass wir Gutscheine erhalten werden und unsere Tickets zwecks Stempelung bereithalten mögen... Öh, mein Telefon wollte ich aber nicht verschönern lassen. Warten wir mal ab was passiert: 'Ach, sie sind das ja. Hm, hier ist der Gutschein, aber bitte nicht schummeln.' - Klar, warum sollte ich auch? Immerhin geht die Fahrt dann doch nach knapp zwei Stunden weiter - in die Gegenrichtung. Wir haben aktuell etwa eine Verspätung von 190 Minuten Ahhhja, 3:10 Stunden klingen wohl zu brutal für die Reisenden oder wie ist die Angabe zu verstehen? Egal, ich gehe auch nicht davon aus, dass wir in irgendeiner Weise Boden gutmachen werden. Hm, das entspricht in etwa 22:00 Uhr Ankunft bei mir, dass klappt jedoch nicht mit der gebuchten Reiseroute, also Plan B und wir disponieren den Weg über Mannheim. Irgendwie habe ich echt Mitleid mit der Zugbegleitung. Nicht nur, dass seit der WM die Bordansagen generell in zwei Sprachen (mehr schlecht denn recht) durchgegeben werden, nein auch die vielen Anfragen mit den alternativen Verbindungen und herrje Problemen und 'angekratzten' Reisenden. Ich glaube, die sind allesamt froh, wenn sie Feierabend haben und ins Wochenende können. We are empty 'Bedingt durch die Verspätung von 3 Stunden und 10 Minuten' - ah, geht doch... - 'weisen wir sie darauf hin, dass wir das Bordrestaurant ausnahmeweise früher schliessen müssen. Dieser ICE wird die Fahrt in Frankfurt Hbf beenden.' - Hehehe, komplett geplündert der Laden hier. Glücklicherweise konnte ich mir rechtzeitig noch was zu Trinken sichern. Die weitere Reiseroute Okay, also, dann eben die Ostroute nach Hause. Statt wie geplant über Bad Münster geht es eben über Mannheim und Kaiserslautern nach Hause. Wir sind ja spontan und flexibel. Ich bin mal gespannt was mich noch alles erwarten wird. Die Umsteigezeit in Ffm beträgt übrigens nur geplante 10 Minuten. Sofern keine weiteren Verzögerungen eintreffen sollten... ... die jedoch wegen der Gleisblockierung nicht ausblieben. Somit wurde der reguläre ICE nach München über Mannheim und Stuttgart eben 8 Minuten später auf dem gegenüberliegenden Gleis erwartet. Fein, die Weiterfahrt bis nach Mannheim blieb dann relativ unkompliziert. Immerhin waren wir noch zu dritt, und nutzten die Zeit mit Smalltalk, wo denn wer herkomme und wohin zu welchem Zweck fahre. Wenn Chaos, dann Chaos Nach einer kurzen Verschnaufpause in Mannheim ging es dann mit der S-Bahn weiter .o( Das nächste Mal nehme ich den IC/EC nach Paris... ). Nur das Chaos wurde wieder größer: jugendliche Fußballfans des 1.FC Saarbrücken. Mann, mann, was ein Gelabbers. Ich kann euch sagen, unglaublich. \uD83E\uDD2A Well, letztendlich brachte mich die RSB zurück in die gewohnte Heimat. Jetzt erstmal ein ausgiebiges Bad zum Relaxen nehmen... Bis denne, JoKi PS: Die logistische Leistung zur Disposition der Verspätungen und Anschlußverbindungen ist übrigens respektabel und verdient mein Lob. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'kaum-lobt-man-die-deutsche-bahn.html'; this.page.identifier = '9271ed1c97_idkaum-lobt-man-die-deutsche-bahn'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "keynote-ahead.html": {
    "href": "keynote-ahead.html",
    "title": "Keynote ahead - Get Blogged by JoKi",
    "summary": "Keynote ahead Copy Markdown View Markdown GitHub Markdown Source Keynote ahead Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'keynote-ahead'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'keynote-ahead'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Keynote ahead') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-02-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Und los geht's... Yeah! Nach den ersten Begrüßungen und Shake-Hands mit einigen Community-Mitgliedern gibt's die Uniform für die nächsten drei Tage: eine dunkelblaue Soft-Shell-Weste mit 'ner coolen Rückenbeflockung: Ask the Experts. Frisch gedressed und stolz wie Oskar geht's wieder zurück ins Foyer der Stadthalle. Lockerer Smalltalk mit weiteren Experten und die ersten bekannten Gesichter aus der CLIP-Community treffen ein. Zwischenzeitlich hatten wir ebenfalls einen 'Mini-Stammtisch Speyer' hier im Foyer: Aktuell konnte ich bereits drei Stammtischteilnehmer begrüßen. Und gerüchteweise sind weitere Personen vom dFPUG Stammtisch anwesend. Wir werden sehen. Weiterhin stehe ich noch in der Bring-Schuld für den Launch der neuen Website für die Terminplanung des Stammtisch. Konform zum Launch der Microsoft Produkte wird's den Launch ebenfalls innerhalb der nächsten drei Tage geben. Die Keynote startet mit einem kurzen Überblick des heutigen Tages und greift den Details für das Party-Event des Abends ein wenig vor. Cooler Stuff wird angeboten. Unter anderem ist das Komikerduo Mundstuhl angekündigt, weitere internationale Music-Acts und und und... Also, die Spannung für heute Abend ist damit definitiv geweckt. Ahhh, gerade wurde auch erläutert, warum überhaupt Karlsruhe... Neben der historischen Grundlage zum Launch vor vier Jahren, stehen die weltweit anerkannte Universität und selbstredend die TechTalks als führende Gründe. Sieht schon witzig aus, so im Kontext der anderen Veranstaltungsorte. Aber okay, ich find's auf alle Fälle genial. ---- VfpStammtischSpeyer GetBlogged if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'keynote-ahead.html'; this.page.identifier = '9271ed1c97_idkeynote-ahead'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "koffer-packen-und-los-gehts.html": {
    "href": "koffer-packen-und-los-gehts.html",
    "title": "Koffer packen und los geht's... - Get Blogged by JoKi",
    "summary": "Koffer packen und los geht's... Copy Markdown View Markdown GitHub Markdown Source Koffer packen und los geht's... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'koffer-packen-und-los-gehts'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'koffer-packen-und-los-gehts'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Koffer packen und los geht's...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-11-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Die 11. Visual FoxPro Entwicklerkonferenz der dFPUG startet am morgigen Tage. Der Sessionplan ist randvoll mit einer hohen Varianz an Themen. Ich nutze den Vortag noch die letzte Schliffe und vor allem zum Koffer packen. Image courtesy of Mikavas if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'koffer-packen-und-los-gehts.html'; this.page.identifier = '9271ed1c97_idkoffer-packen-und-los-gehts'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "kommentar-zu-parameterobjekte.html": {
    "href": "kommentar-zu-parameterobjekte.html",
    "title": "Kommentar zu 'Parameterobjekte' - Get Blogged by JoKi",
    "summary": "Kommentar zu 'Parameterobjekte' Copy Markdown View Markdown GitHub Markdown Source Kommentar zu 'Parameterobjekte' Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kommentar-zu-parameterobjekte'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kommentar-zu-parameterobjekte'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Kommentar zu 'Parameterobjekte'') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-08-25 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Grundlage für diesen Beitrag liefert der Blogeintrag von Olaf Doschke: Parameterobjekte vom 10.06.2006 hier der vollständige Kommentar zu seinem Artikel und meiner 'Weltansicht' zu diesem Sachverhalt: Hallo Olaf, ich finde, dass es ein guter Artikel ist. Wir nutzen das Konzept von Parameter-/Transferobjekten bereits seit Jahren. Dein Ansatz mit den semikolon-separierten Werten (SSV) finde ich soweit auch gut. Bleibt nur die folgende Frage: Wieso Semikolon und nicht Kommata? Eine weitere Frage, oder besser gesagt ein konzeptioneller Gedankengang, geht dahin, dass ich mich frage, wieso der Aufbau der Klasse in den Aufruf der Funktion per Parameter zu erfolgen hat. Der Hintergrund dabei ist, dass man innerhalb eines Projektes eigentlich eine definierte Anzahl von Transferobjekten besitzt, welche auch eine konstante Schnittstelle besitzen sollten. Mein Problem mit deiner Objektdeklaration beim Aufruf geht in die Richtung der Wartbarkeit des Codes. Vom Ansatz her hätte ich das Interface der Funktion in etwa so aufgebaut: Function CreateParameterObject(tcToken As String, tcProperties As String, tcTypes As String, tcValues As String) As Object Über die Namensgebung brauchen nicht zu sprechen... Bei mir würde die Funktion 'GetParameter()' lauten. Die Idee für den Token zielt dahin, dass ich nun innerhalb der Case-Anweisungen ebenfalls eine Art Factory Design Pattern nutzen kann, welches Zugriff auf fertigdefinierte Klassendefinitionen ermöglicht. Diese könnten etwa in einer MetaTabelle(cToken, cProperties, cTypes, cValues) hinterlegt sein. Damit verbessert sich meiner Ansicht nach die Lesbarkeit und vereinfacht sich die Codepflege. Durch die Verwendung von Konstanten kann sich der Informationsgehalt weiter erhöhen. Hier mal ein wenig Beispielcode: Local loPosition m.loPosition = CreateParameterObject(\"Position\") m.loPosition.Left = 100 m.loPosition.Top = 120 Die weiteren Konstellationen sind identisch mit deinem aktuellen Entwurf, sofern der erste Parameter leer bleibt. Also, etwa so: lo = CreateParameterObject(\"\",\"iX;iY\",\"I;I\",\"20;100\") ? lo.iX, lo.iY Eine Kombination wäre sicherlich auch denkbar: #Define PARAMETER_POSITION \"Position\" lo = CreateParameterObject(PARAMETER_POSITION,\"\",\"\",\"20;100\") ? lo.iX, lo.iY In diesem Fall stammen die Informationen zu Eigenschaften und deren Typ aus der Tokenbeschreibung. Hm, was mir noch aufgefallen ist... Was spricht gegen den Fall 'Pcount() == 0'? In dieser Situation verweigert deine gezeigte Implementierung die Rückgabe eines Empty-Objekts, dass übrigens bereits erzeugt wurde... So, jetzt bin ich doch bei der genaueren Betrachtung deines Codes und ziemlich neugierig, ob deiner Entscheidungsgrundlagen. Es ist nämlich sehr interessant darüber zu philosophieren. In deiner Routine nutzt du drei Arrays zur Abbildung der drei Stringparameter in Kombination mit dem Alines()-Statements. Die Möglichkeit zur Definition des Delimiters in Alines() ist wirklich sehr praktisch. Dennoch würde ich in diesem Fall auf die Konvertierung von String nach Array verzichten und direkt mit den vorhandenen VFP-Funktionen GetWordCount() und GetWordNum() arbeiten: For lnIndex = 1 To GetWordCount(tcProperties, \";\") AddProperty(loObject, GetWordNum(tcProperties, lnIndex, \";\")) EndFor Es ist reine Neugier, wieso du mit Arrays arbeitest. Interessiert mich wirklich sehr. Ich denke, dass ich mal beide Varianten auf Performance (Anzahl der Durchläufe in konstanter Zeitspanne) prüfen werde. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'kommentar-zu-parameterobjekte.html'; this.page.identifier = '9271ed1c97_idkommentar-zu-parameterobjekte'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "kommentare-zum-eisbr.html": {
    "href": "kommentare-zum-eisbr.html",
    "title": "Kommentare zum Eisbär - Get Blogged by JoKi",
    "summary": "Kommentare zum Eisbär Copy Markdown View Markdown GitHub Markdown Source Kommentare zum Eisbär Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kommentare-zum-eisbr'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kommentare-zum-eisbr'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Kommentare zum Eisbär') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-13 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Irgendwie reicht die Kommentarfunktion beim Eisbären nicht mehr bzw. die notwendige Funktionalität fehlt im Blog. Daher schreibe ich meine Gedanken und Anmerkungen hier. Also deine Einleitung ist ja schon ein leichter 'Brüller': 'uebrigens, bitte nicht wegen der Umlaute wundern, aber die Terminals hier auf der PDC verfuegen logischerweise nicht ueber deutsche Tastaturen' - Öhem, schon mal was von Lokalisierung gehört? Evtl. auch die Tastenkombination Alt+Shift - Naja, wenn auf der PDC die Terminals keine multiplen Layouts anbieten, zeugt das von ... [zensiert]. Wenn man als Veranstalter einer internationalen Konferenz noch nicht einmal lokalisierte Terminals anbietet... - Neee, ich schreib' nix weiter. Und wieso logischerweise? Öhem, gibt's eigentlich kein WLAN und Steckdosen, dass du deinen Laptop benutzen kannst??? Hehehe, der Eintrag 'Sex mit japanischen Zwillingen' dürfte wohl auf sich warten lassen... zwinker Also, wenn Energieerzeuger wie Software-Entwickler arbeiten würden... Willkommen in der Steinzeit bzw. im atomar verstrahlten dritten Jahrtausend. Ich schweife ab. Hm, in deiner Passage fehlte die Aussage 'I invited the internet' - Ist schon spassig, dass es vor 1985 keine Computer gab... grübel - und das grafische Oberflächen, und generelle Netzwerkfähigkeiten und ... erinnert mich gerade spontan an einen Song von Die Prinzen 'Das ist alles nur gekl...' - oder okay \uD83D\uDE09 Marketing war immer schon eine Stärke. 'das ist ja ein Punkt, wo ich persoenlich Windows weit vor Linux sehe, die Integration zahlreicher Anwendungen' - kennst du den netten Spruch von Dieter Nuhr? Falls nicht, dann können wir uns gerne mal darüber unterhalten. Diese Aussage ist bereits seit Jahren exakt ein sehr häufiger Nachteil in Windows, aber Marketing... 'Toolbars in Tabs ... ein geniales Konzept, wie ich finde, und verdammt uebersichtlich.' - gääääähn - Oh, wie innovativ und so neu... Hallo? Aufwachen! 'dies ist meiner Meinung nach wieder mal ein Beispiel, wo Microsoft erst eine wichtige Entwicklung ewig lang verpennt, es dann aber deutlich umfangreicher und besser umsetzt als die Konkurrenz.' - Hehehe, eine? - Ist es nicht sehr oft so? Wurde wenigstens auch OpenDocument in Office 12 gezeigt? Du kennst meine Meinung dazu. Und ehrlich gesagt, stellen sich andere Hersteller ab und zu auch betriebswirtschaftliche Fragen. Es freut mich, dass Microsoft ausreichend Geld zum Hinterherlaufen besitzt, aber Innovationen wachsen an anderen Stellen. 'Ansonsten bringt Windows eine neue Speichertechnologie namens SuperFetch mit' - Uiiih, sollte sie es wirklich endlich mal schaffen, dass echte Speicherreservierung funktioniert? Ist ja fast undenkbar, aber mit Unterstützung einiger offener Kernelquellen lässt sich da sicherlich was machen. hüstel... 'der Datenzugriff auf SQL-Datenbanken oder sonst eine x-beliebige Datenquelle geht direkt aus C# heraus, und das ganze dermassen einfach ... da sieht selbst Visual FoxPro alt dagegen aus.' - Nun, ein 20 Jahre altes Produkt von Grund auf zu neu zu bauen ist einfacher, ja da gebe ich dir recht, aber mal ehrlich, woher stammen denn die Ansätze für diesen direkten Datenbankzugriff und das DataBinding, hm? Meiner Meinung nach, hat es irgendjmd. bei Microsoft endlich mal verstanden, dass die Technologien von VFP wieder mal begutachtet und neu bewertet werden, so dass man sich von Anfang einiges an Arbeit sparen kann, weil man Bestehendes verbessert und adaptiert. Ist nur aktuell ziemlich interessant, dass die beiden Verantwortlichen ehemalige FoxPro-Gurus sind... Zufall? Verschwörung? Oh Mann, wie blind bist du eigentlich? - 'was die ganze Programmierung auf zwei, drei Zeilen JScript reduziert.' - Klar, und morgen backen alle Himbeereiskuchen. Sobald du nur 'ne Winzigkeit vom vorgeschriebenen Weg abweichen möchtest, kannste dich wieder mit zig Problemen beschäftigen. Zumindest war es bisher immer so, ist es aktuell immer noch und wird es daher auch künftig nicht anders werden. Mein Fazit: Ich finde es wirklich klasse, dass du dir soviel Mühe machst, die Blogeinträge zu schreiben, aber irgendwie scheinst du unterwegs einen Virus abbekommen zu haben - Leichtgläubigkeit. Natürlich wird alles neuer, toller, bunter, einfacher und sicherer werden, aber mit welchem Benefit für einen Entwickler und vielmehr mit welcher Freiheit für einen Entwickler. Weisste einen Anwender interessiert nicht, dass sein Office 23 mit einem Server ad hoc Telemetriedaten austauscht, er möchte ein fehlerfrei funktionierendes Produkt mit einer einfachen Usability. Insofern muss ich dir ganz ehrlich sagen, dass ich dich zukünftig eher Alice oder Dorothy nennen werde. Bis denne, JoKi PS: Ich freu' mich auf kritischere Beiträge und ein wenig mehr Beurteilung der Konsequenzen \uD83D\uDE09 PPS: Nein, ich bin nicht gegen die Entwicklungen bei Microsoft; im Gegenteil! Ich sehe die Dinge aufgrund meiner Kenntnisse nur wesentlich kritischer... oder realistischer. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'kommentare-zum-eisbr.html'; this.page.identifier = '9271ed1c97_idkommentare-zum-eisbr'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "kommune-33.html": {
    "href": "kommune-33.html",
    "title": "Kommune 33 - Get Blogged by JoKi",
    "summary": "Kommune 33 Copy Markdown View Markdown GitHub Markdown Source Kommune 33 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kommune-33'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kommune-33'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Kommune 33') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-02-10 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Alter Schwede! Es ist sicherlich schwierig während der Zeit eines so großartigen Events wie das Ready to Rock the Launch noch eine Übernachtungsmöglichkeit zu finden. Okay, ich bin gewissermaßen selbst schuld, da ich mich relativ spät um eine Unterkunft gekümmert habe, aber okay, es gibt immer Möglichkeiten. Zuerst ging's ins Internet: Hotels in Karlsruhe googlen. Alles klar, nach zwei, drei Anrufen die Ernüchterung... alles ausgebucht, keine Chance! Öhem, Mist. Also, Plan B - Kollegen und Freunde interviewen ob weiterer Ideen und Anregungen. Well, den entscheidenden Impuls erhielt ich von meinem 'Schwager in spe' \uD83D\uDE42 Schau dir mal die folgenden Pensionen an, da ist sicherlich was dabei... Jaoh okay... Erster Anruf - ausgebucht. Zweiter Anruf - ausgebucht. und so weiter... Letztendlich konnte ich im Zoo unterkommen. Im wahrlich übertragenen Sinne des Wortes. Das Wort Pension könnte man in diesem Fall problemlos mit Studenten-Wohnheim der Sechziger ersetzen. De facto handelt es sich um einen renovierten Altbau. Die 'Pension' verteilt sich auf drei bis vier Etagen und die Zimmer sind ganz normale Zimmer mit Bett und Kleiderschrank. Minimalismus für die Nacht pur - coole Sache. Mir soll's egal sein, da ich lediglich ein Dach über dem Kopf und ein Bett unterm Hintern haben möchte. Für die paar Stunden braucht's keinen Anspruch. Die Reinlichkeit ist sehr gut und bedarf keinerlei Kritik - im Gegenteil! Hab ich eigentlich schon etwas über den Host der Pension geschrieben? Nee, okay, dann kurz ein paar Worte dazu... Man könnte eigentlich sagen, dass es sich um einen ehemaligen Langzeitstudenten handelt. Lockeres Alltagsoutfit und ganz im alternativen Uni-Style. Die Formalitäten? Joah, also, hier mal den Meldebogen ausfüllen und that's it. Aber... was ist das? Da ich nur kurz vom Kongresszentrum rübergesprungen bin, hatte ich die Ask the Experts Weste noch an und scheint ihn das Wort Microsoft irritiert zu haben. \uD83D\uDE0E Und yes... ein bekennender Linuxanhänger. Okay, jetzt packen wir die Verwirrungstaktik aus. Nach den 'ersten Platitüden' zu Good and Evil, entkräftige ich jegliche Argumente mit hilfreichen Tipps und kompetenten Informationen zu Linux. Hehehe, was macht ein Linuxer im Microsoft-Dress in Karlsruhe? - Ganz einfach: Das Beste aus beiden Welten nutzen. Das erinnert mich immer an den Spruch 'Der Wolf im Schafspelz' - was jedoch in keinster Weise zutrifft. Ich find's beispielsweise absolut klasse, dass Microsoft in den letzten Jahren als Aussteller auf dem Linuxtag aktiv war und hoffentlich wieder sein wird. Denn... wir leben in einer heterogenen IT-Welt. Es gibt weder das Eine Betriebssystem, noch die Eine Software zum Lösen aller Probleme. Sondern immer die Alternativen und die Wahlmöglichkeiten aus Vielen. Quintessenz: Falls man in Karlsruhe eine lockere, günstige Unterkunftmöglichkeit in Karlsruhe sucht und vielleicht noch im Bereich Linux / Unix unterwegs ist, dann ist die Pension am Zoo empfehlenswert. Mir hat es absolut Spass gemacht und gut geschlafen habe ich auf alle Fälle. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'kommune-33.html'; this.page.identifier = '9271ed1c97_idkommune-33'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "konspiratives-community-treffen.html": {
    "href": "konspiratives-community-treffen.html",
    "title": "Konspiratives Community-Treffen? - Get Blogged by JoKi",
    "summary": "Konspiratives Community-Treffen? Copy Markdown View Markdown GitHub Markdown Source Konspiratives Community-Treffen? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'konspiratives-community-treffen'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'konspiratives-community-treffen'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Konspiratives Community-Treffen?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-13 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Willkommen zur Trainingsrunde des SV Grün/Weiß Mainz. Wie vielleicht schon der ein oder andere geneigte Leser mitbekommen haben könnte, sind meine Community-Kollegen Willi und Tiger in meinen heimatlichen Gefilden unterwegs. Bereits letzte Woche liefen die erste Grobplanungen, die sich am Montag konkretisierten. Thomas sammelt Alex in Mannheim ein, und wir treffen uns dann mir, um den Rest des Abends mit lockerem Klönen und starker Musik im Kult zu verbringen. Also, während die beiden noch auf Tour waren, habe ich mir die Zeit noch mit einem Klassiker auf DVD verdingt: Time Bandits von Terry Gilliam. Herrlich, insbesondere das Interview mit einem französischen Team. It's party time Öhm, oder auch erstmal nicht. Denn beim normalen Durchschleichen der verkehrsberuhigten Zone fiel mir gleich ein verdächtiges Fahrzeug auf. Silverfarbener Mercedes Kombi mit grüner Tarnbeklebung: Los Grünos \uD83D\uDE03 Spasshalber meinte ich noch, dass die gleich wenden werden und uns anhalten. .o( Jaja, labber' du nur... ) Und so war's auch. Scheinbar versprüht Thomas irgendwelche Pheromone - who knows. Okay, also, das übliche... oder auch nicht, denn das hektische Rumfummeln mit der Taschenlampe und der intensiven Inspektion des Fahrzeugs passen nicht zu einer regulären Verkehrskontrolle. Insbesondere wenn die üblichen Hinweise - \"Guten Abend, das ist eine allgemeine Verkehrskontrolle\" - und Fragen - \"Haben Sie etwas getrunken?\" - überhaupt nicht fielen. Sehr suspekt, aber ich würde mir auch heftige Gedanken machen, wenn drei lockere Typen nachts um halb zwölf durch die Gegend tuckern. Schmuggelware? Hehlergut? - Tjoa, leider Fehlanzeige, wir sind anständige Bürger - auch wenn es mit Ankunft der WM anfängt, interessanter für Terroristen zu werden. Leute, es wird nichts so heiß gegessen wie es gekocht wird. Kommt mal wieder auf den Teppich der Normalität... Lady's night Nach der etwa zwanzigminütigen Personalausweisprüfung konnten wir dann noch locker und flockig im Kult einlaufen. Hach, was ein Spass. Hatte ich eigentlich schon erwähnt, dass mittwochs so 'ne Art Lady's Night im Kult stattfindet? Nee, okay, also neben der Tatsache, dass die Musik ziemlich auf alternativ und feminim ausgelegt ist, sind auch überdurchschnittlich viele Frauen unterwegs. Immer wieder ein erheiternder Anblick. Sowohl in eine Richtung, wie auch in die andere. \uD83D\uDE09 Gut, gut. Die ersten bekannten Gesichter sind auch gleich entdeckt und wir gesellen uns gemütlich zu einigen Bekannten. Bei einer gepflegten Runde Bionade - Geschmack Holunder tauschen wir uns über die aktuellen Neuigkeiten aus der Community, dem Universum und dem Leben überhaupt aus. Wirklich spassig, was da seit unserem letzten Treffen auf der CeBIT alles wieder vorgefallen ist. Ich hoffe nur, dass keiner von uns auf die Idee kommt, eine Biographie zu schreiben. Die Anwaltsklagen wären uns gewiß... \uD83D\uDE09 So, aktuell sitzen wir beim Frühstück, mal sehen was der heutige Tag noch bringen wird... Get Blogged soon und bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'konspiratives-community-treffen.html'; this.page.identifier = '9271ed1c97_idkonspiratives-community-treffen'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "krakathon-2017.html": {
    "href": "krakathon-2017.html",
    "title": "Krakathon 2017 - Get Blogged by JoKi",
    "summary": "Krakathon 2017 Copy Markdown View Markdown GitHub Markdown Source Krakathon 2017 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'krakathon-2017'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'krakathon-2017'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Krakathon 2017') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-10-27 - Filed under CommunityActivity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Pirates! Pirates everywhere! This year my children urged me to bring them again to the coolest hackathon in Mauritius. They absolutely loved the experience from last year and so the anticipation grew over the last couple of weeks. The Krakathon is in their own words \"Le Krakathon, c’est comme un hackaton mais avec un Kraken et un trésor!\". This translates freely to: The Krakathon is like a hackathon but with a Kraken and a treasure! Code to chase the Kraken! The whole event is build around a pirate and swashbuckler stylish atmosphere where participants form a crew (team of up to 4 members) to solve algorithms and programming challenges. Compared to other events the Krakathon doesn't have a single overall aim asking teams to compete against each other to achieve the best outcome on the same request. There is no jury to vet it. No, the Krakathon uses a catalog of coding exercises. Given the level of difficulty they are assigned various amounts of points. The challenge is to implement and demonstrate working solutions and to accumulate as many points as possible during 24 hours. The progress of a team and the position among each other is updated and published regularly on the Krakathon Scoreboard online. Transparency is key! And it is great for others to follow their team remotely, and to cheer them up on social media, too. The Flying Craftsman Thanks to the generousity of ict.io it was possible to form a ship crew to represent the Mauritius Software Craftsmanship Community again. Like last year, The Flying Craftsman was manned at an early stage and was ready to sail off the shores. The crew under Captain Saif was freshly put together, and unfortunately had to be shuffled a few days ahead of the event. Left to right: Saif, Wasim, Arvind and Alvin Luckily, we talked about the Krakathon during the weekly Code & Coffee breakfast meetings of the MSCC and Alvin, a digital nomad from Malaysia actually, expressed high interest. Instead of just visiting the Krakathon he boarded The Flying Craftsman and was right in the middle of the chase for the Kraken. I'm loving this community! Spirit of the Sea 24 hours doesn't sound much for sailing on the ocean but coding straight all the time is cumbersome and tiring. A real physical and mental challenge - hats off to all pirates! The final standing of this year's search for the Kraken is as following: ForceCoders Pickle Rick Xtensio The Flying Craftsman came out 6th. All teams found their treasure and rewards after this coding marathon. Personally, I'm glad to see that more and more opportunities like this are available in Mauritius. And thanks to the numerous partners and sponsors Krakathon turns out to be one of the coolest. Surely an event not to miss in the future! Check the captains' log books Although I'm not an active participant in the Krakathon I like going there to cheer up all those brave mates. And as mentioned initially, my children are super excited and eager to be there, too. Don't miss the reports on the Krakathon by others with lots of pictures and videos: Krakathon 2017 - un Hackathon mais avec un Kraken et un trésor - jour 1 by Pritvi Krakathon 2017 - Day 1 by Bilaal Krakathon 2017 - Day 2 by Bilaal Krakathon 2017 by Akasha Krakathon 2017: Les ForceCoders remportent la deuxième édition du code challenge! by ict.io Krakathon: les «Force Coders» remportent la 2e édition by lexpress.mu Last but not least, Krakathon 2018 has its countdown already. Aye aye, pirates! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'krakathon-2017.html'; this.page.identifier = '9271ed1c97_idkrakathon-2017'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "kuehe-auf-nachtwanderung.html": {
    "href": "kuehe-auf-nachtwanderung.html",
    "title": "Kühe auf Nachtwanderung - Get Blogged by JoKi",
    "summary": "Kühe auf Nachtwanderung Copy Markdown View Markdown GitHub Markdown Source Kühe auf Nachtwanderung Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kuehe-auf-nachtwanderung'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kuehe-auf-nachtwanderung'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Kühe auf Nachtwanderung') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-15 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Mal wieder eine Anekdote aus dem privaten Bereich. Was passiert wenn man mal länger in der Feuerwehr zusammen mit den Kameraden den Abend gestaltet und daher nach Mitternacht Richtung Domizil fährt? Nun, in unserer ländlichen Gegend scheinen des Bauers Kühe erweiterten Auslauf zu haben... Ich weiß ja nicht wie man die nächtlichen Herumtreiber sonst verstehen sollte, aber ich glaube, dass den beiden Kälbern schlichtweg zu langweilig auf der Weide war. Wir hatten auf alle Fälle unseren Spass. Zuerst Sturmklingeln beim Kuhbesitzer, dann noch einen weiteren Mann zur Unterstützung aus den Federn ziehen und dann 'bewaffnet' mit Warnblinker vom Fahrzeug, zwei Taschenlampen quer über die unteren Wiesen, um die Milchviecher auf die Koppel zu treiben. Wohl gemerkt, irgendwann nachts um halb zwei oder so... Ich fand's cool! Zumal es den Alltag abwechselungsreich gestaltet. Hast du auch schon mal Kühe bei der Nachtwanderung begleitet? Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'kuehe-auf-nachtwanderung.html'; this.page.identifier = '9271ed1c97_idkuehe-auf-nachtwanderung'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "kung-shee-fat-choy.html": {
    "href": "kung-shee-fat-choy.html",
    "title": "Kung Shee Fat Choy - Get Blogged by JoKi",
    "summary": "Kung Shee Fat Choy Copy Markdown View Markdown GitHub Markdown Source Kung Shee Fat Choy Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kung-shee-fat-choy'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'kung-shee-fat-choy'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Kung Shee Fat Choy') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-02-08 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Happy New Year in chinese language. Today is Chinese Spring Festivaland we are celebrating the start of a new chinese year. You may think'Why are you celebrating?' Well, the answer is straight forward. Mylovely girlfriend and future wife is half chinese. Any more questions? And together with all the family we are enjoying this public holiday inMauritius. The interesting difference here is that the date of 'NewYear' variies from year to year compared to the occidential knowledge,whereas Silvester is always on the 31st of December. But still thereare a lot of fireworks and fire crackers in the surroundings. It'sreally nice to see and hear those spectacles. So, to all of you living the asian way of live: Kung Shee Fat Choy Enjoy your life and stay well. PS: While writing these lines the next two(!) cyclones, namely Hondoand Ivan, are already on the satellite images and trajectory. Let's seewhat the next two days will offer. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'kung-shee-fat-choy.html'; this.page.identifier = '9271ed1c97_idkung-shee-fat-choy'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "launch-event-windows-10-and-visual-studio-2015.html": {
    "href": "launch-event-windows-10-and-visual-studio-2015.html",
    "title": "Launch Event: Windows 10 and Visual Studio 2015 - Get Blogged by JoKi",
    "summary": "Launch Event: Windows 10 and Visual Studio 2015 Copy Markdown View Markdown GitHub Markdown Source Launch Event: Windows 10 and Visual Studio 2015 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'launch-event-windows-10-and-visual-studio-2015'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'launch-event-windows-10-and-visual-studio-2015'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Launch Event: Windows 10 and Visual Studio 2015') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-08-06 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The month July 2015 marked two more great milestones in the history of Microsoft. First, on the 20th of July Visual Studio 2015 has been released and second, just a couple of days later on the 29th of July was the global launch of Windows 10. Both latest incarnations of world-wide known and used products coming from Redmond. Back at the begin of June I already got in touch with our local Technical Evangelist asking whether there might be any plans regarding those two activities. And the following weeks I bumped my questions here and there in order to see what's going on here on \"Cyber Island\". Finally, on the 21st of July I got an official invitation to attend the Windows 10 Launch: Windows 10 official event invitation at Microsoft Indian Ocean Islands & French Pacific No question whether to attend or not... Windows 10, Visual Studio 2015 and... Visual Studio Code Yes, you read it correctly - Visual Studio Code, Microsoft's newly launched cross-platform text editor for quick and easy access to redefined code editing features. Surprisingly, Arnaud got in touch with me just some days prior to the event asking whether it would be suitable to actually do a presentation on Visual Studio Code - best running it on Linux, too. As I just wrote an article regarding an enhanced experience of using Code on Linux I agreed and all was set for the event to happen. T - 30 minutes... Even though it's officially after office hours and therefore after the usual rush hour in Port Louis, I didn't want to gamble on being late and left a bit earlier. Luckily, the short trip to the capital went smooth and it was quite fluid to drive, even in Port Louis itself. At approximately 18:00 hrs I finally arrived in front of Dias Pier Building at the Caudan Waterfront, and more or less bumped into fellow tweeter Cedric. Most interestingly it was the first time ever that we met \"offline\" but we recognised each other on the spot. Well, off to the Microsoft office then... Some of the Microsoft Student Partners (MSP) were already around and it seemed that another technical session on C# development was still in full swing. Great, lots of geeks and familiar faces around. Next, I spoke to Arnaud and we checked the schedule of sessions for the evening quickly. Cool, my session on Visual Studio Code is going to be the sugar icing on top of a promising evening - last one! As there was still plenty of time we went to check the available equipment for the presentation, and the fun started. As I'm using an older HP laptop running on Xubuntu 15.04 with a VGA output only but the projector didn't want to accept this input source we had to look for alternatives in order to give the audience some first hand experience with this awesome editor. Well, using an USB-HDMI converter based on DisplayLink wasn't properly recognised by the kernel and I didn't want to go through some of the pain I had with an external USB display, I suggested that I'm going to run the demo on a virtualised environment on my main machine. Luckily, I still had a freshly installed Xubuntu 14.04 as an image. Based on my own step-by-step guide and after transferring some source code folders I was ready for prime-time. Lots of professional networking and expert exchange during the Windows 10 event Networking before, during and after the presentations Honestly, it's always great to attend social events for software developers, eh craftsmen. Those unique opportunities to meet with other geeks in the field are undeniable and there should be more events like this throughout the year. Apart from getting introduced to new peeps and got quite a number of questions regarding the past and future Developers Conference. Surely, the event back in April was a great success and personally I'm very pleased with the outcome especially after a short preparation time of less than 4 months. Right now, there are already plans and small preparations in the pipe for 2016, more to come soon. Also, please read the blog articles from other fellows attending: Windows 10 and Visual Studio 2015 launch event in Mauritius Windows 10 & Visual Studio Launch Event at Microsoft Indian Ocean Islands Windows 10 Launch Party in Mauritius They wrote a bit more about the details of the sessions of the evening. I'm going to spare you with that one... \uD83D\uDE09 Talking about Visual Studio Code Well, it was more or less just a cameo session of roughly 20 minutes. Actually, I went quickly through the steps of acquiring Code from the official website, gave some details about the nature of the application. Code itself is written in TypeScript using web-based technologies which are then hosted in the cross-platform compatible Electron shell which runs on Windows, Mac OS X and Linux operating systems. Next, there were the steps from my guide, and after showing the shortcut entry in the main application menu it was time to fire up Code itself. Showing source code of an AngularJS web app with controller and partial view next to each other in Visual Studio Code Thanks to the high quality even at this early stage of Code there were no surprises and I showed some features, like the usual syntax highlighting capabilities of a variety of programming languages, the builtin image and icon display, as well as the neat integration of git client. Using the AngularJS source code and the partial views in HTML of the website of the developers conference I also demo'd the split view feature of Code. Which is actually pretty handy to have controller and view source code next to each other while developing your web sites or web applications. And last but not least, I switched virtual desktop and showed the last two, three features in Visual Studio Code on Windows, too. Just to demonstrate that the UI and user experience is identical between operating systems. Code is surely a great tool for mixed teams of software and web developers. Eventually, you might also have a look at the options to customise your key bindings - just to make it more comfortable to your personal taste. Thanks After the launch event of Windows 8 back in October 2012 where I met Arnaud Meslier first time there had been some great development in terms of ICT here in Mauritius. Apart from the activities of the Linux User Group of Mauritius (LUGM) and the Mauritius Software Craftsmanship Community (MSCC) there are more and more opportunities for like-minded people to meet and exchange offline. Apart from the introduction of great products and tools this evening, it was a pleasure for me to have a wide range of chats with other geeks. Thanks, and the next event is hopefully staged already... \uD83D\uDE09 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'launch-event-windows-10-and-visual-studio-2015.html'; this.page.identifier = '9271ed1c97_idlaunch-event-windows-10-and-visual-studio-2015'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "learn-to-code-in-swift.html": {
    "href": "learn-to-code-in-swift.html",
    "title": "Learn to Code in Swift: The new language of iOS Apps by Kevin McNeish - Get Blogged by JoKi",
    "summary": "Learn to Code in Swift: The new language of iOS Apps by Kevin McNeish Copy Markdown View Markdown GitHub Markdown Source Learn to Code in Swift: The new language of iOS Apps by Kevin McNeish Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'learn-to-code-in-swift'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'learn-to-code-in-swift'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Learn to Code in Swift: The new language of iOS Apps by Kevin McNeish') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2016-02-28 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); As a subscriber of a mailing list curated by Kevin McNeish I received his call to action around begin of December last year, Call for action: Review Kevin's book \"Learn to Code in Swift\" and following his request I sent him an email that I'd be interested to read and review his title. Luckily, I was among the first 20 to respond and after some quick exchange of emails I had his book title \"Learn to Code in Swift: The new language of iOS Apps\" on my Kindle device. Today, I left the following review on Amazon: Solid content delivery of the concepts of Swift - Easy to understand Frankly, I know Kevin since more than a decade, attended one of his five-days workshop, and he simply keeps on delivering solid, high quality content that is logically structured, easy to read and to understand. Despite being a seasoned software developer in other programming languages I was very pleased when Apple introduced their completely renewed and well-designed programming language Swift. And although I'm more focused on using C# on Xamarin to develop smartphone and mobile application I always like to keep an eye on other technologies. As Swift is surely one of them I thought to give Kevin's book a shot... And he has delivered as expected. Only by looking at the table of content I knew that this is going to be a fun read. The book is actually structured like a set of full-day classes or better said a training workshop for beginners in Swift. It starts with the basic elements of Swift, then covers code workflow and finishes of with more advanced topics like closures and error handling. Throughout the chapters there are some samples which have some hidden gems (for those knowing Kevin, his family and friends more closely). Those samples put a smile on my face and gave me a couple of chuckles, like in chapter 10 when Kevin explains the handling of arrays in Swift using \"well-known\" names as array elements. Unfortunately, I'm giving a 4 stars rating only for two reasons: The amount of either typographic, grammar or contextual errors. The book version I had at the time reading had at least an error in every chapter. Some of them are quite obvious just by reading through the chapters, others are a bit trickier, ie. explanations in text don't match the illustrations or diagrams. Surely, the quality in this area could be better with a little bit of editorial activities. Formatting issues and display of images or videos. Actually, I read the book using three different devices - a classic Kindle, an iPad mini using the Kindle app, and the web edition of the Kindle app. First, on the classic e-ink-based Kindle some of the images either didn't load at all or the display was too blurry or too dark to enjoy the visual appearance. Same applies to videos which wouldn't even play (but this could be due to my low-bandwidth internet connection). Second, after finishing 'Chapter 22: Generics in the Real World' you'll get the Amazon book rating screen despite two more chapters and appendices to read. Luckily, these issues are easy to fix, and I hope that Kevin is going to provide an update soon. I'd like to close my book review with a paragraph from the 'About the author' chapter which sums it up beautifully: \"I learned that writing software is a very creative process. In just a matter of hours, I could conceive an idea, create a software design and have it up and running on a computer.\" This book on learning how to write software in Swift is highly recommended. Eventually, you might be interested to give it a shot right here and check out the free book preview of Learn to Code in Swift by Kevin McNeish. Even though I develop mainly in C# and using Xamarin to develop iOS apps I have to admit that reading Kevin's book was very informative and helped me to get a better insight intormation to iOS application development in general. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'learn-to-code-in-swift.html'; this.page.identifier = '9271ed1c97_idlearn-to-code-in-swift'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "learning-content-for-mcsds-web-applications-and-windows-store-apps-using-html5.html": {
    "href": "learning-content-for-mcsds-web-applications-and-windows-store-apps-using-html5.html",
    "title": "Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5 - Get Blogged by JoKi",
    "summary": "Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5 Copy Markdown View Markdown GitHub Markdown Source Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'learning-content-for-mcsds-web-applications-and-windows-store-apps-using-html5'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'learning-content-for-mcsds-web-applications-and-windows-store-apps-using-html5'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-11-16 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Recently, I started again to learn for various Microsoft certifications. First candidate on my way to MSCD: Web Applications is the Exam 70-480: Programming in HTML5 with JavaScript and CSS3. Motivation to go for a Microsoft exam I guess, this is quite personal but let me briefly describe my intentions to go that exam. First, I'm doing web development since the 1990's. Working with HTML, CSS and Javascript is happening almost daily in my workspace. And honestly, I do not only do 'pure' web development but already integrated several HTML/CSS/Javascript frontend UIs into an existing desktop application (written in Visual FoxPro) inclusive two-way communication and data exchange. Hm, might be an interesting topic for another blog article here... Second, this exam has a very interesting aspect which is listed at the bottom of the exam's details: Credit Toward Certification When you pass Exam 70-480: Programming in HTML5 with JavaScript and CSS3, you complete the requirements for the following certification(s): Programming in HTML5 with JavaScript and CSS3 Specialist Exam 70-480: Programming in HTML5 with JavaScript and CSS3: counts as credit toward the following certification(s): MCSD: Web ApplicationsMCSD: Windows Store Apps using HTML5 So, passing one single exam will earn you specialist certification straight-forward, and opens the path to higher levels of certifications. Preparations and learning path Well, due to a newsletter from Microsoft Learning (MSL) I caught interest in picking up the circumstances and learning materials for this particular exam. As of writing this article there is a promotional / voucher code available which enables you to register for this exam for free! Simply register yourself with or log into your existing account at Prometric, choose the exam for a testing facility near to you and enter the voucher code HTMLJMP (available through 31.03.2013 or while supplies last). Hurry up, there are restrictions... As stated above, I'm already very familiar with web development and the programming flavours involved into this. But of course, it is always good to freshen up your knowledge and reflect on yourself. Microsoft is putting a lot of effort to attract any kind of developers into the 'App Development'. Whether it is for the Windows 8 Store or the Windows Phone 8 Store, doesn't really matter. They simply need more apps. This demand for skilled developers also comes with a nice side-effect: Lots and lots of material to study. During the first couple of hours, I could easily gather high quality preparation material - again for free! Following is just a small list of starting points. If you have more resources, please drop me a message in the comment section, and I'll be glad to update this article accordingly. Developing HTML5 Apps Jump Start This is an accelerated jump start video course on development of HTML5 Apps for Windows 8. There are six modules that are split into two video sessions per module. Very informative and intense course material. This is packed stuff taken from an official preparation course for exam 70-480. Developing Windows Store Apps with HTML5 Jump Start Again, an accelerated preparation video course on Windows 8 Apps. There are six modules with two video sessions each which will catapult you to your exam. This is also related to preps for exam 70-481. Programming Windows 8 Apps with HTML, CSS, and JavaScript Kraig Brockschmidt delves into the ups and downs of Windows 8 App development over 800+ pages. Great eBook to read, study, and to practice the samples - best of all, it's for free. Read my review on this title. codeSHOW() This is a Windows 8 HTML/JS project with the express goal of demonstrating simple development concepts for the Windows 8 platform. Code, code and more code... absolutely great stuff to study and practice. Microsoft Virtual Academy I already wrote about the MVA in a previous article. Well, if you haven't registered yourself yet, now is the time. The list is not complete for sure, but this might keep you busy for at least one or even two weeks to go through the material. Please don't hesitate to add more resources in the comment section. Right now, I'm already through all videos once, and digging my way through chapter 4 of Kraig's book. Additional material - Pluralsight Apart from those free online resources, I also following some courses from the excellent library of Pluralsight. They already have their own section for Windows 8 development, but of course, you get companion material about HTML5, CSS and Javascript in other sections, too. Introduction to Building Windows 8 Applications Building Windows 8 Applications with JavaScript and HTML Selling Windows 8 Apps HTML5 Fundamentals Using HTML5 and CSS3 HTML5 Advanced Topics CSS3 etc... Interesting to see that Michael Palermo provides his course material on multiple platforms. Fantastic! You might also pay a visit to his personal blog. Hm, it just came to my mind that Aaron Skonnard of Pluralsight publishes so-called '24 hours Learning Paths' based on courses available in the course library. Would be interested to see a combination for Windows 8 App development using HTML5, CSS3 and Javascript in the future. Recommended workspace environment Well, you might have guessed it but this requires Windows 8, Visual Studio 2012 Express or another flavour, and a valid Developers License. Due to an MSDN subscription I working on VS 2012 Premium with some additional tools by Telerik. Honestly, the fastest way to get you up and running for Windows 8 App development is the source code archive of codeSHOW(). It does not only give you all source code in general but contains a couple of SDKs like Bing Maps, Microsoft Advertising, Live ID, and Telerik Windows 8 controls... for free! Hint: Get the Windows Phone 8 SDK as well. Don't worry, while you are studying the material for Windows 8 you will be able to leverage from this knowledge to development for the phone platform, too. It takes roughly one to two hours to get your workspace and learning environment, at least this was my time frame due to slow internet connection and an aged spare machine. \uD83D\uDE09 Oh, before I forget to mention it, as soon as you're done, go quickly to the Windows Store and search for ClassBrowserPlus. You might not need it ad hoc for your development using HTML5, CSS and Javascript but I think that it is a great developer's utility that enables you to view the properties, methods and events (along with help text) for all Windows 8 classes. It's always good to look behind the scenes and to explore how it is made. Idea: Start/join a learning group The way you learn new things or intensify your knowledge in a certain technology is completely up to your personal preference. Back in my days at the university, we used to meet once or twice a week in a small quiet room to exchange our progress, questions and problems we ran into. In general, I recommend to any software craftsman to lift your butt and get out to exchange with other developers. Personally, I like this approach, as it gives you new points of view and an insight into others' own experience with certain techniques and how they managed to solve tricky issues. Just keep it relaxed and not too formal after all, and you might a have a good time away from your dull office desk. Give your machine a break, too. Updates First, I passed exam 70-480 successfully. Second, there is more content for your preparation. Thanks to Michael Palermo's blog article I stumbled upon these great instructions by Matthew Hughes (hint: start at the bottom) to get yourself prepared for exam 70-480. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'learning-content-for-mcsds-web-applications-and-windows-store-apps-using-html5.html'; this.page.identifier = '9271ed1c97_idlearning-content-for-mcsds-web-applications-and-windows-store-apps-using-html5'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "learning-from-jquery.html": {
    "href": "learning-from-jquery.html",
    "title": "Learning from jQuery - Solid fundament for experienced jQuery developers - Get Blogged by JoKi",
    "summary": "Learning from jQuery - Solid fundament for experienced jQuery developers Copy Markdown View Markdown GitHub Markdown Source Learning from jQuery - Solid fundament for experienced jQuery developers Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'learning-from-jquery'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'learning-from-jquery'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Learning from jQuery - Solid fundament for experienced jQuery developers') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-05-20 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Frankly speaking, I had to sleep a night over before typing this review. And even now it is not an easy, straight-forward task to write this recension. I'm not sure whether I'm the right kind of audience this title is actually addressed to. It clearly states that this book is for web developers which are very familiar with jQuery library but would like to extend their knowledge to vanilla JavaScript. Not being part of this particular group it felt strange to go through the various chapters after all. This title is clearly addressed to experienced jQuery users and developers especially while looking for improvements in performance and better ways of optimisations. Sometimes just to simplify the existing jQuery code in order to avoid the heavy load of the complete jQuery library and sometimes for the better understanding of JavaScript and its syntax. Callum's style of writing is clear and the numerous code samples used to emphasize the various techniques are good ones and easy to understand. Quite interestingly, it put a light smile on my face when I compared his sample code of sending an AJAX request to some code in one of my own blog articles I wrote back in 2006 (in German language). JavaScript is clearly a mature language and certain requirements are simply done this way. And Callum explains the nuts and bolts of JavaScript very well. Personally, I gained most out of this book from chapter 5 - JavaScript Conventions. The paragraphs and code snippets on Optimizations and Common Antipatterns gave me a better understanding on various aspects of JavaScript development, and I definitely have to revise a couple of code fragments I have written in the past. Overall the book provides solid information on JavaScript for jQuery developers and is worth the money spent. Just be sure that you're part of the targeted audience. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'learning-from-jquery.html'; this.page.identifier = '9271ed1c97_idlearning-from-jquery'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "lets-rock-with-my-and-maria.html": {
    "href": "lets-rock-with-my-and-maria.html",
    "title": "Lets rock with MySQL and MariaDB - Get Blogged by JoKi",
    "summary": "Lets rock with MySQL and MariaDB Copy Markdown View Markdown GitHub Markdown Source Lets rock with MySQL and MariaDB Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lets-rock-with-my-and-maria'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lets-rock-with-my-and-maria'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Lets rock with MySQL and MariaDB') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-01-02 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Some weeks months ago... What happens on Facebook I saw an announcement made by Ronny on Facebook that he's about to organise a meeting about MySQL and MariaDB. Well, I have to admit that I didn't have that much contact with Ronny but I knew that he was involved in the initiation of the Linux User Group of Mauritius (LUGM), and that Ish already mentioned his name a couple of times, mostly because of the inspirational approach and other funny things. Well, long story short. Ronny mentioned that one of his friends will be around on the island for some vacation and that said person agreed to do a session on the history, the (eventual) future and some technical aspects of MySQL and MariaDB. Sounds great and having an expert from abroad doesn't happen too often... Okay, next Ronny was looking for a decent location and I suggested to him that he might his luck at The Flying Dodo Brewery in Bagatelle. In general not a problem but those guys over there speak money and in order to get their side room with some conference aspirations they wouldn't agree on the usual deal for user groups. Meaning: Room for attendees consuming food & drinks. As I had personal interest in this session to happen, I backed Ronny's intentions to go forward with it and to let me know in case that there financial constraints to be expected. Running your business provides you with some benefits and allowances. Anyway, there was a little fee for the evening to be paid, and I was glad to cover those expenses through my business: IOS Indian Ocean Software Ltd. Lets rock with MySQL and MariaDB The \"event\" was scheduled for the evening hours, and after the official part it was commonly agreed that we are going to leverage the location and have a decent after-meeting session at the brewery. It's always nice to combine work with pleasure - particularly in that specific order. Our presenter, an international consultant for MySQL and MariaDB working at SkySQL AB at that time, named Joffrey Michaie did a great job during the evening. First, he gave us a brief history lesson about the origins of MySQL, then elaborated on the recent purchase event during the last couple of years and went over the actual reasons why MariaDB has been created. Well, Sun and Oracle did a great job to get quite a number of good developers on MySQL as well as the community on their feet. The fork of MySQL into MariaDB is reasonable given that Oracle doesn't need to support two opposing RDBMS within the same company - astounishingly that's a very familiar constellation seeing Microsoft SQL Server and Microsoft Visual FoxPro (VFP) in the past. Anyway, approximately 90% (and more) of the original MySQL developers quit their job and went over to a company called SkySQL AB - which is solely temporarily and there had been a press release recently, that it's now officially MariaDB AB. Monty Widenius had his coup and the core development team is back to its roots. And... best of all: MariaDB is an inplace-replacement for MySQL. In case that you're operating your website or blog on MySQL you can simply install and use MariaDB instead of. It works flawlessly. Next, Jojo gave us some corner data about the wide-spread use of MySQL/MariaDB. Actually some big internet companies or better said their websites (like Facebook, SAP, Xing, etc.) are driven by MySQL installations spread over hundreds or even over thousands of machines. Of course, this requires some interesting architecture not only regarding the physical setup of machines and networks but also in terms of storage and replication features. High-availability (HA) is the magical keyword in this case. At a certain size you have to switch towards DB clusters and Joffrey gave us good information about one could setup such clusters using Galera. He also gave us a brief overview of some specialised storage engines available in MySQL/MariaDB which definitely go far beyond the capabilities of the standard types like MyISAM or InnoDB. Full screen entertainment for geeks The full presentation of a whooping 107 slides is available on SlideShare - Thanks to Joffrey and the LUGM! MariaDB Presentation by Joffrey Michaie On my side, I have to admit that I was a bit interruptive as I had a good number of questions regarding certain features I'm used to using either VFP or SQL Server. Especially given the fact that I was involved in the software architecture and development of client-server applications that run on roughly 100 instances of SQL Server including different types of data replication. Yes, we did partitioning and the database has a variety of replication scenarios for different tables; including typical master-slave replication but also enhanced 2-way replication. Also dealing with data volumes in 2-digit and even 3-digit regions is not unusual with my clients. And there is quite a difference between writing and running queries against a low amount of records compared to tables with 15+ million records. Not to forget about write and update operations. Patiently, Joffrey took note of my questions and he had very good answers how certain setups and requirements could be solved and handled with MariaDB. One of the interesting topics was the discussion about data types of \"uniqueidentifier\" versus \"UUID\" versus \"Global Transaction ID (GTID)\". Well, basically they are the same... Whereas SQL Server handles replication based on that specific data type, MySQL or MariaDB remains on dealing with integer-based column data types (comparable to Auto-Increment in SQL Server) - which I find problematic. MariaDB is not just the database anymore; it's a platform for application developers and database administrators Anyway, the evening had some interesting chunks of information for me and I enjoyed the whole presentation. Joffrey knows how to keep the audience focused and engaged into the topic. And shamelessly we extending the scheduled 1-hour session by at least 30 minutes or so. Until all questions have been asked and answered. And after all this talking and listening it was time to move over to the social aspects of the evening and to get some refreshments. Networking session and future activities Later on I managed to have a little smalltalk with Jojo and even though the meeting was under the aegis of the LUGM, I informed him about the existence, goals and intentions of the Mauritius Software Craftsmanship Community (MSCC). Dunno, how he took it but since then we are still in touch on social media networks, and have a chat from time to time. On my part I'm looking forward to the next opportunity to hear about MariaDBfrom Joffrey - and of course I won't hesitate to act as a sponsor again. Oh, and thanks for the goodies - I really like that black MariaDB 10 T-Shirt. Disclaimer: Images are courtesy of MariaDB Corporation Ab. MariaDB is a trademark or registered trademarks of MariaDB Corporation Ab in the European Union and United States of America and/or other countries. MySQL is a trademark of Oracle Corporation Inc. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'lets-rock-with-my-and-maria.html'; this.page.identifier = '9271ed1c97_idlets-rock-with-my-and-maria'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "lily-of-the-valley.html": {
    "href": "lily-of-the-valley.html",
    "title": "Lily of the Valley - Get Blogged by JoKi",
    "summary": "Lily of the Valley Copy Markdown View Markdown GitHub Markdown Source Lily of the Valley Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lily-of-the-valley'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lily-of-the-valley'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Lily of the Valley') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-17 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hmm, das kommt davon wenn man sich mal das Monatsprogramm im Kult anschaut - Live Act! Wow, hier im Kult einen Live Act? - Genial, ich glaub' meinen letzten Live Act im Kult hab ich vor 10 Jahren oder so mitgenommen. Damals war's 'ne Doors Coverband, die richtig geilen Sound und Stimmung rübergebracht hat. Alter Schwede! Well, alternative Rockmusik mit Folklore-Touch - ich glaub' das dürfte es am ehesten treffen... Lily of the Valley sind zwei Mädels, die seit einigen Jahren europäische Musikkultur in ihre Songs einfliessen lassen und nutzen. Ich glaub' die beiden haben an dem Abend mal locker 10 Instrumente in ihrer Performance genutzt. Querflöte und Akkordeon dürften noch die ausgefallensten gewesen sein. Anfangs dachte ich nur - Oh, Mann, wo bin ich hier nur gelandet? -, aber mit der Zeit wurde es richtig cool und ehrlich gesagt, ziemlich chillig... Die Tanzfläche war als Stage umgebaut und die beiden nutzen die geringe Größe, um spontan das Publikum beim Musizieren einzuspannen. Nice! Glücklicherweise konnte ich mir einen lockeren Platz mit vollem Überblick ergattern und im Laufe des Abends hatte ich noch ein sehr ausgelassenes Gespräch mit meiner Nächsten... Das ist Kult! Zu meiner Schande muss ich gestehen, dass ich das Gespräch bei manchen Songs viel interessanter fand. Aber die Musik hat super in den Background gepasst. Und so habe ich mir im Affekt auch die aktuelle CD der beiden zugelegt. Wenn schon, denn schon... oder? Gegen Mitternacht erfolgte die Übergabe an den DJ und yeah, die passende Musik zur Stimmung des Abends wurde aufgelegt. Ist schon cool, wenn man Mike Oldfield in der 'Disko' auf die Ohren bekommt. Mal sehen, wann sich das Gespräch weiterführen lässt. War echt spassig! Bis denne, JoKi Achja, auf dem Heimweg lief selbstredend Lily of the Valley im CD-Player... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'lily-of-the-valley.html'; this.page.identifier = '9271ed1c97_idlily-of-the-valley'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "linq-auch-mit-vfp-kein-problem-vosql.html": {
    "href": "linq-auch-mit-vfp-kein-problem-vosql.html",
    "title": "LINQ - auch mit VFP kein Problem: VOSQL - Get Blogged by JoKi",
    "summary": "LINQ - auch mit VFP kein Problem: VOSQL Copy Markdown View Markdown GitHub Markdown Source LINQ - auch mit VFP kein Problem: VOSQL Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'linq-auch-mit-vfp-kein-problem-vosql'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'linq-auch-mit-vfp-kein-problem-vosql'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'LINQ - auch mit VFP kein Problem: VOSQL') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-21 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Achja, wer sagt's denn. Kaum sind die ersten Sprößlinge von LINQ zu lesen, da gibt's ein gleichartiges Proof-of-Concept in Visual FoxPro dazu. Hätte mich auch ehrlich gesagt ein wenig gewundert, wenn es mit einer datenbank-zentrierten Programmiersprache nicht möglich wäre, einen objektorientierten Ansatz gemäß zukünftigem .NET Framework (Orcas - Version 3.0) zu realisieren. Die ausführlichen Details und den Quellcode könnt ihr euch im Blog von Craig Boyd anschauen: Project LINQ - Can it be done in VFP? please remember that I've spent a total of about 1 hour on this and part of that time was spent getting a list of countries and stats for the example. I just wanted to show a proof-of-concept for now. - nette Aussage... Ansonsten bleibt nur zu sagen, dass ich mir auch schon mal Gedanken zu einer entsprechenden OO-Umsetzung für den Datenzugriff gemacht hatte, aber ehrlich gesagt diesen Ansatz aus Performancegründen ad acta gelegt hatte. Nunja, wir werden sehen was die Zukunft uns bringen wird. Spannend ist es allemal. Aber bevor ich mir weiter Gedanken über diesen Ansatz mache - den ich übrigens sehr gut finde - spiele ich lieber weiter an meiner Implementierung von FoxSQL. Vllt. später mal dazu. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'linq-auch-mit-vfp-kein-problem-vosql.html'; this.page.identifier = '9271ed1c97_idlinq-auch-mit-vfp-kein-problem-vosql'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "listed-at-foxblogs-tagcloud.html": {
    "href": "listed-at-foxblogs-tagcloud.html",
    "title": "Listed at FoxBlogs (TagCloud) - Get Blogged by JoKi",
    "summary": "Listed at FoxBlogs (TagCloud) Copy Markdown View Markdown GitHub Markdown Source Listed at FoxBlogs (TagCloud) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'listed-at-foxblogs-tagcloud'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'listed-at-foxblogs-tagcloud'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Listed at FoxBlogs (TagCloud)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-12-29 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Just like to mention, that ITA Software provides an OMPL list of blogs about Visual FoxPro and related stuff. I like this kind of compressed information. I already was very confident with the list of bloggers at the FoxWiki - https://fox.wikis.com/wc.dll?WikiBlogWatchPeople But using OMPL is very cool! Thanks to Rick Borup for providing this useful service. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'listed-at-foxblogs-tagcloud.html'; this.page.identifier = '9271ed1c97_idlisted-at-foxblogs-tagcloud'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "live-and-let-live-modern-laziness.html": {
    "href": "live-and-let-live-modern-laziness.html",
    "title": "Live and let live - modern laziness... - Get Blogged by JoKi",
    "summary": "Live and let live - modern laziness... Copy Markdown View Markdown GitHub Markdown Source Live and let live - modern laziness... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'live-and-let-live-modern-laziness'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'live-and-let-live-modern-laziness'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Live and let live - modern laziness...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-06-28 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wow, it has been a while since I managed to visit the author's corner of my blog. Well, quite frankly there had been a variety aspects and quite a number of excuses to avoid coming to this place and I'd like to write a little bit about it. Eventually, this article might be a little bit boring for some of the readers but I would like you to join the comment section at the bottom - maybe after reading the following lines. Thank you! Stay sharp! Well, gratefully I have to report that the first ever Developers Conference had quite some positive impact on the local IT scene and we managed to reach out to some international audience, too. Apart from the (mostly) positive feedback of participants, speakers and sponsors alike, I would like to mention that the event received a little bit of media coverage, too. Despite our (short) preparation period of approximately 3.5 months and a small core organisation team of 3 people, I would say that we had quite some fun and hopefully were able to deliver some interesting content. But quite frankly I would like to remind people here on the island that it is about taking action and doing things... don't just sit there, talk about potential ideas and throw them overboard just because one or two people in your inner circle are discouraging and pointing out \"so many problems and eventualities\". Remember, Rome wasn't built in a day and adventuring a journey of 1,000 miles starts with a first single step. Taking a vacation from my blog But coming back to this blog, I have to admit that I was simple taking a mental vacation from it. The preparation period of the conference as well as running the event in high gears was kind of exhausting - physically and mentally. And hereby, I would like to express my deepest gratitude and thanks to my loving wife, Mary Jane, and my two little monsters for their patience and light short-comings in terms of \"paying attention\", \"being around\", and of course \"daddy-time in general\". Thanks and I love you all... And of course, there is the business side of the medal. Spending quite some time on arrangements with potential sponsors, having frequent exchange with potential speakers, and getting the conference web site in shape was kind of exhausting. But don't forget, I'm a small business owner, and spending time on such extra-ordinary activities has actually a double-impact. Yes, it does. Any non-productive time, economically speaking, directly implicates less incoming money on the bank account. So, while I was really enjoying the extra-time donated to community and creating more awareness of the Mauritius Software Craftsmanship Community in general, I had to keep an eye on the financial situation of things, too. You can't just stop doing your daily job... Well deserved refreshment after the preparations and successful execution of the first ever Developers Conference in Mauritius Obviously, there had been some catch-up to be done right after the event in order to keep my clients and my chief financial officer (read: BWE) happy. And so, instead of hanging out on the beach enjoying the perks of living on a tropical island, this meant to keep the caps on the keyboard warm and in constant movement. Like a well-oiled machinery that just needs some TLC from time to time I was hacking down lines of code during the last two months actually. Again, I really do love my profession and from that point of view, it never felt like an obligation or burden to get rid off of a pile of queued up work. Aux contraire, it felt great to be \"back\" and stay focused on coding lines after lines for my customers' requirements. Lack of inspiration? Now after roughly seven to eight weeks in, I'm still not sure whether it feels right to finally pick up blogging again. Honestly, due to narrow focus on work assignments - even though being busy with a variety of different contexts - I was waiting for the right moment to write here on the blog. Maybe you might have a similar experience that even though you have a fantastic idea to blog about... that there's a lack of inspiration, or should I say passion?, to actually sit and starting the article. Well, tonight it just feels right. The last two weeks had been great and I'm feeling the energy coming back to me. Right now, I'm again in a position where there's a little extra time... and some light cuddles of inspiration are whispering into my ears. \uD83D\uDE09 Yeah, that sounds kind of awkward... Being occupied with other activities Yes, trust me on that one... \uD83D\uDE09 Apart from writing code for customer projects and assignments I have been busy reading a good number of technical books on different topics. Again, I' have a good feeling about writing some reviews during the next couple of weeks. Most titles were of technical nature but there had been one or two titles from different genres in-between. And, not to forget to mention I also got some event invitations which I had to attend to as well. Honestly, the last two months had been great in all kind of flavours - hahaha, expect for entertaining this blog - and I'm really looking forward to share some of those experiences with my readers. And there are going to be some technical entries, too. Anyway, I kept my notes of interesting topics I would like to pin down here on the blog, and during the upcoming weeks I'm confident to share my thoughts on various aspects of software development, community activities in the IT world of Mauritius, and of course life in general. Stay tuned for more chapters to come... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'live-and-let-live-modern-laziness.html'; this.page.identifier = '9271ed1c97_idlive-and-let-live-modern-laziness'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "lndliches-brandenburg.html": {
    "href": "lndliches-brandenburg.html",
    "title": "Ländliches Brandenburg - Get Blogged by JoKi",
    "summary": "Ländliches Brandenburg Copy Markdown View Markdown GitHub Markdown Source Ländliches Brandenburg Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lndliches-brandenburg'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lndliches-brandenburg'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Ländliches Brandenburg') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-13 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Anreise Im Gegensatz zur Hinreise nach Prag verlief die Rückkehr nach Deutschland relativ unspektakulär. Morgens mit Bus und Metro zum Flughafen, lockeres Einchecken und diesmal ohne unvorhergesehene Wartezeiten zurück nach Berlin Tegel. Von dort aus ging die Reise per Bahn weiter in die ländlichen Regionen von Teltow-Flaeming im südlichen Brandenburg. Schon die Fahrt im Regional-Express durch die morgendliche Sonne verhieß Gutes für den weiteren Tagesverlauf. Die Gegend hier - Teltow-Fläming bzw. Der Teltow - ist einfach nur traumhaft: Wiesen- und Waldregionen soweit das Auge reicht - und das weitestgehend flach. Herrlich, sicherlich ein Paradies für Sportler... Laufen, Radfahren und Skaten. Letzte Woche auf dem AFP-Workshop wurde bereits die Information in die Gespräche eingestreut, dass hier extra eine Skaterbahn über mehrere Kilometer angelegt sei. Klingt verlockend... Acodey-Workshop Die nächsten Tage werden wir uns mit diversen Komponenten aus unserer Bibliothek Acodey beschäftigen. Die Anforderungen sind durch den Kunden vorgegeben und wir schauen, welche Ergebnisse wir gemeinsam produzieren können. Weiterhin besteht die Notwendigkeit zur Verwendung eines Baum-Controls. Nichts leichter als das... \uD83D\uDE01 - schliesslich nutzen wir diesen bereits ausgiebig und erfolgreich in eigenen Projekten. Heute beschäftigen wir uns erst noch ein wenig mit Zugriff auf multiple VFP-Datenbanken über die Klassen DataEnvironment und VfpDataBehavior, und bereinigen das Projekt samt Entwicklungsverzeichnis. Tilt... Der Akku neigt sich dem Ende. Kein Wunder - durfte ich doch um 4:30 oder so heute morgen aufstehen. Nach Anreise und etlichen Stunden Workshop freue ich mich über gemütliches Zusammensitzen beim Abendessen mit zwei Teilnehmer, die im gleichen Hotel nächtigen. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'lndliches-brandenburg.html'; this.page.identifier = '9271ed1c97_idlndliches-brandenburg'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "long-time-ago---.html": {
    "href": "long-time-ago---.html",
    "title": "Long time ago... - Get Blogged by JoKi",
    "summary": "Long time ago... Copy Markdown View Markdown GitHub Markdown Source Long time ago... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'long-time-ago---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'long-time-ago---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Long time ago...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-12-28 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); ...in a country far far far away. I guess this may be the words for a fairy tale. But this blog article isn't to be one of those. Well, long time ago since I blogged my last article on this site. Meanwhile I did a lot of project stuff for some customers and now again, it's vacation time. Probably the best time to write a new blog entry. Alright, let's what's changed since last time. Actually I wrote some nice entries to my German blog at https://jochen.kirstaetter.name/ and like to summarize the last three months a little bit. Starting with the German VFP developer's conference - https://devcon.dfpug.de - should be a smooth way to sum up all this stuff. Yup, again I had the chance to speak at the conference and I held four sessions. Those were about useful and free tools to enhance daily work with Visual FoxPro (D-GEIZ), Regular Expressions - my favorite one (D-REGX), using Subversion as source code version control system (D-SVN) and last but not least about interoperability between Visual FoxPro and Linux related technologies (D-LINU). Just check out the session descriptions for details or even better the UT Coverage at https://www.utcoverage.com/German/2005 and https://www.utcoverage.com/German/20052 - especially the picture gallery. \uD83D\uDE09 It was a lot of fun during these days and I appreciate next year's event. Additionally I'll head to Praha again. Maybe we'll meet each other. Fine, talking about German FoxPro User Group (dFPUG) I'd like to mention that we just had our last meeting of the regional user group at Speyer - the city here in Germany with the famous dome. Yeah, it was a quite funny event because we had some nice goodies from O'Reilly, Microsoft and others as give-aways. Plans for 2006 are already made and I bet that this will be one of the best opportunities to get in touch with die-hard FoxPro coders in our region. \uD83D\uDE09 One of the monthly attendees uses Fox since FoxBase 1.02b (IIRC) - that's way impressive. And here we are at the present moment. Currently I did a lot of enhancements in Html Plugin for Active FoxPro Pages and AfpWiki itself. I hope to release both products during next week. Html Plugin is primarily a plug-in for Active FoxPro Pages. But that's only half the truth. The plug-in itself is build as a component and offers stand-alone functionality for VFP developers and through COM as well for other programming languages. In VFP you just create an object like so: Set Procedure To \"html.plugin.dll\" Additive Html = CreateObject(\"CHTML\") If you have to use COM instead, then this works just fine: Html = CreateObject(\"AFPPlugin.Html\") It was quite funny to get this component working in .NET Framework and I'm still working on this topic, but at the moment I got it to create my own namespace (tree) based on this single DLL. After you added a reference to the COM server, you'll have an new namespace tree instead of just one plain entry: ProLib.AFP3.Plugin.Html and due to COM interoperability in .NET the instance name of the class is simple HtmlClass. I wrote a sample solution that's able to convert an RSS Feed to a .NET DataSet through the Html Plugin. Well, not too hard to implement in pure .NET but the fact is, that the plug-in provides the same functionality to VFP developers. And... Html Plugin is a core part of AfpWiki - a Wiki engine on top of Active FoxPro Pages. Actually the Html Plugin does all the stuff related to Regular Expressions. It's really nice to see that just one line of code parses all hyperlinks of the entire document and converts them into clickable links with 'title' attribute and protocol indicator. There is still a lot of work to do inside of AfpWiki, like backup and restore of the database, online upgrade mechanism as well as an abstraction layer to user authentication and management, but this will be implemented during a couple of weeks. You might check out https://faq.dfpug.de to see the system in action. Ah, before I forget to mention... Html Plugin and AfpWiki are both available for free (as in beer g). So, if anyone of you is interested in a Wiki solution... just drop me a note. That's all for 2005... I wish you A Happy New Year and hope to see you all in 2006 again. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'long-time-ago---.html'; this.page.identifier = '9271ed1c97_idlong-time-ago---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "loss-of-power-even-wo-cyclone.html": {
    "href": "loss-of-power-even-wo-cyclone.html",
    "title": "Loss of power - even w/o cyclone - Get Blogged by JoKi",
    "summary": "Loss of power - even w/o cyclone Copy Markdown View Markdown GitHub Markdown Source Loss of power - even w/o cyclone Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'loss-of-power-even-wo-cyclone'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'loss-of-power-even-wo-cyclone'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Loss of power - even w/o cyclone') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-02-03 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It's quite funny to write these lines at the moment... because the place I live is completely without any electricity. So, you may wonder 'How is able to write a blog article?' - Hehehe, quite easy. Using a laptop and internet connectivity via mobile. Simple but effective. Also interesting is the matter of fact that there are no bad weather conditions or whatever that seems to be of natural sources. Hm, so I guess that someone hit a pole on the road and cut the power line. Yeah, things like this can happen. We gonna know tomorrow (hopefully). Anyways, I need to spare some battery power for work and close this article with the following sentence: It's amazing to see the stars w/o any light source in the surroundings. So long and good night from Mauritius. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'loss-of-power-even-wo-cyclone.html'; this.page.identifier = '9271ed1c97_idloss-of-power-even-wo-cyclone'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "lost-in-the-twilight-zone---.html": {
    "href": "lost-in-the-twilight-zone---.html",
    "title": "Lost in the Twilight Zone... - Get Blogged by JoKi",
    "summary": "Lost in the Twilight Zone... Copy Markdown View Markdown GitHub Markdown Source Lost in the Twilight Zone... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lost-in-the-twilight-zone---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lost-in-the-twilight-zone---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Lost in the Twilight Zone...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-12 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich glaube so dürfte es Golo heute ergangen sein... ...nachdem er Zugang zum Tracking-System der Active FoxPro Pages erhalten hat. \uD83D\uDE09 Wie kam's dazu? Ganz einfach eigentlich, wir haben einen Eintrag, der es ermöglichen soll, dass die Active FoxPro Pages ohne irgendwelche Installationsorgien ad hoc out-of-the-box genutzt werden sollen. Sozusagen XCopy-Deployment auf dem Webserver. Dies dürfte insbesondere für die kleineren Hosting-Angebote interessant werden, da wir hier auf etliche Limitierungen eingehen und diese umgehen. Nunja, wir werden sehen. Letztendlich hatten wir intensiv über einen Eintrag gesprochen und diverse Ansätze und Lösungsmöglichkeiten erörtert. Irgendwann kamen wir dann mal auf die Idee, was denn an Voraussetzungen vorhanden ist und welche Bedingungen zu erfüllen sind. Und dann fing der Spass an... Also, Spass für mich, aber Golo hat's irgendwie anders aufgenommen. See for yourself - dürfte wohl am besten passen. Schon der erste Blick in die Twilight Zone sorgte bereits für das ein oder andere Stirnrunzeln, von den Mund-auf-Luft-hol-Mund-zu-Aktivitäten möchte nicht berichten. Erwähnte ich schon das Kopfschütteln mit erstauntem Blick? \uD83D\uDE09 Okay, letztendlich erhielt er einen Account und damit ist er dann endgültig versunken - Ich hab' ihn seitdem nicht mehr gesichtet, geschweige denn gehört - rituelle Andacht bei ihm? Well, ich bin mal gespannt, ob er bis morgen die Trackingliste mit unseren Ansätzen und Absichten für künftige Versionen der Active FoxPro Pages durch ist, oder ob's doch länger brauchen wird. Welcome to the desert of the real world \uD83D\uDE09 Ich bin mal auf die ersten Reaktionen gespannt... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'lost-in-the-twilight-zone---.html'; this.page.identifier = '9271ed1c97_idlost-in-the-twilight-zone---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "lugm-corsair-hackers-reboot.html": {
    "href": "lugm-corsair-hackers-reboot.html",
    "title": "Corsair Hackers Reboot - Get Blogged by JoKi",
    "summary": "Corsair Hackers Reboot Copy Markdown View Markdown GitHub Markdown Source Corsair Hackers Reboot Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lugm-corsair-hackers-reboot'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lugm-corsair-hackers-reboot'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Corsair Hackers Reboot') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-04-21 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It wasn't easy for me to attend but it was absolutely worth to go. The Linux User Group of Mauritius (LUGM) organised another get-together for any open source enthusiast here on the island. Strangely named \"Corsair Hackers Reboot\" but it stands for a positive cause: \"Corsair Hackers Reboot Event A collaborative activity involving LUGM, UoM Computer Club, Fortune Way Shopping Mall and several geeks from around the island, striving to put FOSS into homes & offices. The public is invited to discover and explore Free Software & Open Source.\" And it was a good opportunity for me and the kids to visit the east coast of Mauritius, too. Perfect timing It couldn't have been better... Why? Well, for two important reasons (in terms of IT): End of support for Microsoft Windows XP - 08.04.2014 Release of Ubuntu 14.04 Long Term Support - 17.04.2014 Quite funnily, those two IT dates weren't the initial reasons and only during the weeks of preparations we put those together. And therefore it was even more positive to promote the use of Linux and open source software in general to a broader audience. Getting there ... Thanks to the new motor way M3 and all the additional road work which has been completed recently it was very simple to get across the island in a very quick and relaxed manner. Compared to my trips in the early days of living in Mauritius (and riding on a scooter) it was very smooth and within less than an hour we hit Centrale de Flacq. Well, being in the city doesn't necessarily mean that one has arrived at the destination. But thanks to modern technology I had a quick look on Google Maps, and we finally managed to get a parking behind the huge bus terminal in Flacq. From there it was just a short walk to Fortune Way. The children were trying to count the number of buses... Well, lots and lots of buses - really impressive actually. What was presented? There were different areas set up. Right at the entrance one's attention was directly drawn towards the elevated hacker's stage. Similar to rock stars performing their gig there was bunch of computers, laptops and networking equipment in order to cater the right working conditions for coding/programming challenge(s) on the one hand and for the pen-testing or system hacking competition on the other hand. Personally, I was very impresses that actually Nitin took care of the pen-testing competition. He hardly started one year back with Linux in general, and Kali Linux specifically. Seeing his personal development from absolute newbie to a decent Linux system administrator within such a short period of time, is really impressive. His passion to open source software made him a living. Next, clock-wise seen, was the Kid's Corner with face-painting as the main attraction. Additionally, there were numerous paper print outs to colour. Plus a decent workstation with the educational suite GCompris. Of course, my little ones were into that. They already know GCompris since a while as they are allowed to use it on an IGEL thin client terminal here at home. To simplify my life, I set up GCompris as full-screen guest session on the server, and they can pass the login screen without any further obstacles. And because it's a thin client hooked up to a XDMCP remote session I don't have to worry about the hardware on their desk, too. The next section was the main attraction of the event: BYOD - Bring Your Own Device Well, compared to the usual context of BYOD the corsairs had a completely different intention. Here, you could bring your own laptop and a team of knowledgeable experts - read: geeks and so on - offered to fully convert your system on any Linux distribution of your choice. And even though I came later, I was told that the USB pen drives had been in permanent use. From being prepared via dd command over launching LiveCD session to finally installing a fresh Linux system on bare metal. Most interestingly, I did a similar job already a couple of months ago, while upgrading an existing Windows XP system to Xubuntu 13.10. So far, the female owner is very happy and enjoys her system almost every evening to go shopping online, checking mails, and reading latest news from the Anime world. Back to the Hackers event, Ish told me that they managed approximately 20 conversion during the day. Furthermore, Ajay and others gladly assisted some visitors with some tricky issues and by the end of the day you can call is a success. While I was around, there was a elderly male visitor that got a full-fledged system conversion to a Linux system running completely in French language. A little bit more to the centre it was Yasir's turn to demonstrate his Arduino hardware that he hooked up with an experimental electrical circuit board connected to an LCD matrix display. That's the real spirit of hacking, and he showed some minor adjustments on the fly while demo'ing the system. Also, very interesting there was a thermal sensor around. Personally, I think that platforms like the Arduino as well as the Raspberry Pi have a great potential at a very affordable price in order to bring a better understanding of electronics as well as computer programming to a broader audience. It would be great to see more of those experiments during future activities. And last but not least there were a small number of vendors. Amongst them was Emtel - once again as sponsor of the general internet connectivity - and another hardware supplier from Riche Terre shopping mall. They had a good collection of Android related gimmicks, like a autonomous web cam that can convert any TV with HDMI connector into an online video chat system given WiFi. It's actually kind of awesome to have a Skype or Google hangout video session on the big screen rather than on the laptop. Some pictures of the event LUGM: Great conversations on Linux, open source and free software during the Corsair Hackers Reboot LUGM: Educational workstation running GCompris suite attracted the youngest attendees of the day. Of course, face painting had to be done prior to hacking... LUGM: Nadim demoing some Linux specifics to interested visitors. Everyone was pretty busy during the whole day LUGM: The hacking competition, here pen-testing a wireless connection and access point between multiple machines LUGM: Well prepared workstations to be able to 'upgrade' visitors' machines to any Linux operating system Final thoughts Gratefully, during the preparations of the event I was invited to leave some comments or suggestions, and the team of the LUGM did a great job. The outdoor banner was a eye-catcher, the various flyers and posters for the event were clearly written and as far as I understood from the quick chats I had with Ish, Nadim, Nitin, Ajay, and of course others all were very happy about the event execution. Great job, LUGM! And I'm already looking forward to the next Corsair Hackers Reboot event ... Crossing fingers: Very soon and hopefully this year again \uD83D\uDE03 Update: In the media The event had been announced in local media, too. L'Express: Salon informatique: Hacking Challenge à Flacq if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'lugm-corsair-hackers-reboot.html'; this.page.identifier = '9271ed1c97_idlugm-corsair-hackers-reboot'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "lugm-meeting-17082013-mugg-a-bean-bagatelle.html": {
    "href": "lugm-meeting-17082013-mugg-a-bean-bagatelle.html",
    "title": "LUGM meeting - 17.08.2013 - Mugg & Bean, Bagatelle - Get Blogged by JoKi",
    "summary": "LUGM meeting - 17.08.2013 - Mugg & Bean, Bagatelle Copy Markdown View Markdown GitHub Markdown Source LUGM meeting - 17.08.2013 - Mugg & Bean, Bagatelle Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lugm-meeting-17082013-mugg-a-bean-bagatelle'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'lugm-meeting-17082013-mugg-a-bean-bagatelle'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'LUGM meeting - 17.08.2013 - Mugg & Bean, Bagatelle') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-08-13 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Okay, indirect advertisement! Next meetup of the Linux User Group of Mauritius is scheduled for Saturday, 17.08.2013 at 15:00hrs at Mugg & Bean, Bagatelle: Linux User Group of Mauritius is a computer users group comprising of like-minded geeks with the passion to make computing something fun (: We are dedicated to Linux and the FOSS community. We strive to promote free software, open standards and to generally have a good time by hosting social events that educate, allow members to share ideas and knowledge and to build friendship. This particular meetup will focus on : Introducing LUGM to new members & students A brief intro about Linux, Free Software & open licenses Short presentation on openSUSE Advocate Program LUGM : https://lugm.org/about More details and RSVP are available on the Facebook event here if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'lugm-meeting-17082013-mugg-a-bean-bagatelle.html'; this.page.identifier = '9271ed1c97_idlugm-meeting-17082013-mugg-a-bean-bagatelle'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "magazine-von-level-extreme-frei-verfgbar.html": {
    "href": "magazine-von-level-extreme-frei-verfgbar.html",
    "title": "Magazine von Level Extreme frei verfügbar - Get Blogged by JoKi",
    "summary": "Magazine von Level Extreme frei verfügbar Copy Markdown View Markdown GitHub Markdown Source Magazine von Level Extreme frei verfügbar Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'magazine-von-level-extreme-frei-verfgbar'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'magazine-von-level-extreme-frei-verfgbar'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Magazine von Level Extreme frei verfügbar') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-28 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wow! Ich konnte die Mitteilung heute morgen erst nicht richtig fassen, aber in der Tat Level Extreme gibt seine beiden bekannten Zeitschriften Universal Thread Magazine und Level Extreme .NET Magazine an alle Mitglieder von UniversalThread frei. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'magazine-von-level-extreme-frei-verfgbar.html'; this.page.identifier = '9271ed1c97_idmagazine-von-level-extreme-frei-verfgbar'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "managing-joomla-via-android.html": {
    "href": "managing-joomla-via-android.html",
    "title": "Managing Joomla via Android - Get Blogged by JoKi",
    "summary": "Managing Joomla via Android Copy Markdown View Markdown GitHub Markdown Source Managing Joomla via Android Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'managing-joomla-via-android'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'managing-joomla-via-android'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Managing Joomla via Android') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-11-28 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Surprisingly, it was only today that I actually looked for possible solutions to write more content for my blog. Since quite some time I'm using my Samsung Galaxy Tab 10.1 for all kind of social media activities like Google+, FB, etc. but also for my casual mail during the evening hours. And yes, I feel a little bit guilty about missing the chance to use my tablet to write some content here... OK, only a little bit. \uD83D\uDE09 These are not the droids you are looking for But those lazy times are over! While searching the Play Store with the expression 'joomla' I got three interesting hits: Joomla Admin Mobile! Joooid Joomla! Security Checklist After reading the reviews I installed the two later apps. Joomla! Security Checklist The author clearly outlines here that the app is primarily for his personal purpose to have safety checklist at hand at anytime. I guess that any reader of this article has an Android based smartphone or tablet, so that simple app should be part of your toolbox when using Joomla! for your websites. Joooid plugin & app Although I was looking for an app that could work with the default XML RPC interface of Joomla I have to admit that this combination of an enhanced Web service suits me better, mainly due to performance reason. The official website has not only the downloads for Joomla versions 1.5 - 2.5 but also very good and easy to follow step-by-step instructions to prepare your server for the Android app. It will take you less than 5 minutes to get it up and running. For safety reasons, I recommend that you should configure your Web server to have an additional authentication layer on the plugins folder. The smartphone app has the ability to run against HTTP authentication. Personally, I like the look and feel of the app. It is a little bit different compared to the web UI but still easy to use. In fact, this article is the first one written in the Joooid app. At the moment, I only miss the ability to have list tags. Quick and easy Writing full-fledged articles with images, a couple of hyperlinks and some styling here and there should be left to the desktop. At least for the moment. Let's see whether I'm going to change my mind on this during the upcoming months... I'll give it a try, and hope to publish at least once per month to write some content using Joooid. Actually, it would be great to have some feedback about other Joomla! clients in the wild. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'managing-joomla-via-android.html'; this.page.identifier = '9271ed1c97_idmanaging-joomla-via-android'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "marathon--die-dritte.html": {
    "href": "marathon--die-dritte.html",
    "title": "Marathon, die Dritte - Get Blogged by JoKi",
    "summary": "Marathon, die Dritte Copy Markdown View Markdown GitHub Markdown Source Marathon, die Dritte Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-dritte'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-dritte'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Marathon, die Dritte') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-08 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); gäääääähn Wenn ich denjenigen erwische, der mich nach 1.5h Schlaf nach Zahnpasta fragt, wehe... Aber dieses Risiko muss man einplanen, wenn im Großzelt nächtigt. Nunja, egal. Tube raus, murmelndes Gebrabbel von sich geben und wieder umdrehen. Nur irgendwann nimmt der Verkehrslärm vor und im Zelt halt doch überhaupt. Insbesondere, wenn eine 'Horde' junger Mütter mit den Frischlingen rasten. Ich habe Kiddies echt gerne, aber nicht nach nur knapp 3h Schlaf... aber okay. Sowas ist ebenfalls einzuplanen. So, wenn wir schon ein Duschzettel für die Läufer bereitstellen, dann gilt es jenes auch zu testen. Flupp und schon geht's los... Well, Wasser ist auf alle Fälle aus der Leitung gekommen und ich konnte danach wenigstens wieder aus den Augen schauen. Wisst ihr was genial ist? - Wenn man frisch geduscht zurück ins Zelt kommt und das Frühstück ist fertig. Mann, dass ist der Oberhammer. Frische Rühreier und Butterbrot... schleckerlecker So, heute ist der Tag der Läufer - und es sind einige gewesen. Zwischenzeitlich ging gar nix mehr weiter. Zuschauer, Läufer, Familienangehörige der Läufer, achja, dann Skater, und Läufer, und... ja Läufer, dann wieder Kiddies jeglichen Alters, und Kinderwägen, sagte ich schon Läufer? Es war wirklich amüsant den wusselndenMenschenhaufen von leicht erhöhter Position zu beobachten. Wir hatten auf alle Fälle unseren Spass. Aufgabentechnisch gesehen, hatten wir lediglich darauf zu achten, dass in unserem Zelt ausreichend heißes Wasser für die Sportler zur Verfügung steht. Und da gab's keinerlei Probleme. Achja, das Mittagessen... Frische Spaghetti mit selbst zu bereiteter Bolognesesauce (erwähnte ich, dass die Tomaten frisch waren?) und/oder al Pesto (ebenfalls selbst erzeugt...) - Wie auch schon am Vortag hat unser Chefkoch zuviel zubereitet... Ein heiterer Spass. Ansonsten, bleibt nicht mehr viel zu berichten. Am frühen Abend ging's an den gemeinschaftlichen Abbau und auf die Rückfahrt. In der Wehr das Material noch sofort versorgt und die Fahrzeuge wieder entsprechend flott gemacht. Ich glaub' gegen 20:00 oder so, war Schicht im Schacht - schliesslich war Muttertag, nicht wahr... Und ich war auch ziemlich froh, dass ich den Schlaf der Gerechten nachholen durfte. Hm, glücklicherweise habe ich in der Formel 1 an diesem Sonntag nur wenig verpasst. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'marathon--die-dritte.html'; this.page.identifier = '9271ed1c97_idmarathon--die-dritte'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "marathon--die-erste.html": {
    "href": "marathon--die-erste.html",
    "title": "Marathon, die Erste - Get Blogged by JoKi",
    "summary": "Marathon, die Erste Copy Markdown View Markdown GitHub Markdown Source Marathon, die Erste Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-erste'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-erste'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Marathon, die Erste') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-04 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ahhhhh Herrlich, wenn man nach vier (4!) Stunden total entspannt aus der Badewanne steigt. Phantastisch! Ich glaub', es gibt nur eine Sache, die mehr Spass macht... Auf alle Fälle habe ich nun ein weiteres Buch zu Ende gelesen. Es handelt sich dabei einen Titel aus der Belletristik - Tod und Teufel von Frank Schätzing - und ich kann euch das Buch nur wärmstens an Herzen legen. Es handelt sich dabei um einen spannenden und geschickt geschriebenen 'Krimi'-Roman, der im 13. Jahrhundert in der Domstadt Köln handelt. Primär geht's dabei um die Fehde zwischen kirchlichen und weltlichen Interessen in Form des Erzbischofs von Köln und den ansässigen Patriziern. Und einen unfreiwilligen Helden in der Geschichte... Wirklich lesenswert. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'marathon--die-erste.html'; this.page.identifier = '9271ed1c97_idmarathon--die-erste'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "marathon--die-vierte.html": {
    "href": "marathon--die-vierte.html",
    "title": "Marathon, die Vierte? - Get Blogged by JoKi",
    "summary": "Marathon, die Vierte? Copy Markdown View Markdown GitHub Markdown Source Marathon, die Vierte? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-vierte'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-vierte'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Marathon, die Vierte?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-08 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Herrje, ich bin im Marathon-Fieber, sozusagen im Laufwahn... Nee, eigentlich nicht, aber aktuell geht doch einiges ab. Gestern, heute und wahrscheinlich auch morgen stand und steht einiges dem Tagesplan für die Programmierung. Daneben sind noch die weiteren Schritte für den Webcast zu organisieren. Ich denke, dass ich am Wochenende mal dran gehen werde und das grundsätzliche Konzept weiter ausformuliere. Achja, und noch 'ne Kleinigkeit in PHP coden... Jaja, die Vergangenheit holt einen doch immer wieder ein. Naja, ich bin mal gespannt. Vor allem auf VS.php - mal sehen, ob ich das Teil unter VS 2003 zum Laufen bringe und für die Entwicklung nutzen kann. Maguma und Komodo sind mir in der Hinsicht zu ungewohnt... Hmm, am Freitag ist auf alle Fälle mal ein kurzer Arbeitstag (hoffentlich) angesetzt. Schliesslich möchte man ja auch was vom Wochenende haben und sich ein wenig erholen können. So, jetzt aber noch 'ne Kleinigkeit zu Abend futtern - der Börek duftet bereits ziemlich gut aus dem Backofen... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'marathon--die-vierte.html'; this.page.identifier = '9271ed1c97_idmarathon--die-vierte'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "marathon--die-zweite.html": {
    "href": "marathon--die-zweite.html",
    "title": "Marathon, die Zweite - Get Blogged by JoKi",
    "summary": "Marathon, die Zweite Copy Markdown View Markdown GitHub Markdown Source Marathon, die Zweite Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-zweite'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'marathon--die-zweite'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Marathon, die Zweite') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-06 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es ist Samtag, kurz 13:30 und ich warte... Wir treffen uns gegen 14:00 in der Fw für den gemeinsamen Abmarsch zum Gutenberg Marathon in Mainz. Wie auch bereits die letzten Jahre - nur dieses Jahr bin ich auch dabei. Sonst hatte ich immer nur beim Packen und später beim Aufräumen geholfen, aber dieses Jahr nicht... Also, los geht's... Kaum vor Ort geht das Chaos schon los. Baustelle... Parkende Autos von gehfaulen Personen an den Stellen, an denen unsere Zelte und Fahrzeuge stehen sollen. Nunja, wir haben ja Zeit, die Rennen sind ja erst morgen. Insofern: Don't Panic! Well, nach fast 2h warten, stehen unsere Fahrzeuge endlich richtig und der Aufbau beginnt. Wichtiges Zelt ist natürlich unsere mobile Küche in Vollausstattung. Kühlschränke, Bräter, Gasgrill und sogar mobile Spielmaschine sind vor Ort und werden angeschlossen. Herrliche Aktion, schliesslich ist für den Abend frische Paella geplant. Wir kommen ziemlich zügig voran und das Duschzelt steht ebenfalls nach kurzer Zeit. Was tun sprach Zarathustra? - Ganz klar, Essen fassen. Als Vorspeise gibt's Frühlingssalat. Und so schwelgen die Abendstunden dahin. Erfreulicherweise schauen auch etliche Kameraden aus anderen Gefahrstoffzügen und -einheiten bei uns rein. Fröhliches Wiedersehen und lockere Fachgespräche sind angesagt - ich fühl' mich sauwohl. Achja, die Paella - Leute, das hast du nicht gesehen - wirklich phantastisch, frische Miesmuscheln und frischer Tintenfisch, dazu leckere Scampi und alles frisch zubereitet. Ich glaub', dass dürfte 'Schlemmen wie Gott in Frankreich' ziemlich nahe kommen. Beinahe hätte ich es vergessen. Selbstverständlich gab's noch gepflegten Rotwein dazu. Und dabei bin ich dann auch geblieben... Und geblieben... Und geblieben... Ja, und irgendwann wurd's langsam wieder hell und Zeit für den Schlafsack. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'marathon--die-zweite.html'; this.page.identifier = '9271ed1c97_idmarathon--die-zweite'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mastering-the-matrix-qr-code-generation.html": {
    "href": "mastering-the-matrix-qr-code-generation.html",
    "title": "Mastering the Matrix: Elegant QR Code Generation - Get Blogged by JoKi",
    "summary": "Mastering the Matrix: Elegant QR Code Generation Copy Markdown View Markdown GitHub Markdown Source Mastering the Matrix: Elegant QR Code Generation Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mastering-the-matrix-qr-code-generation'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mastering-the-matrix-qr-code-generation'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Mastering the Matrix: Elegant QR Code Generation') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2026-08-23 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Open any modern web browser on your desktop and look closely at the address bar or context menu. Google Chrome provides a built-in Create QR Code for this page tool, complete with its pixelated offline dinosaur mascot sitting proudly in the middle. Microsoft Edge features a dedicated sharing flyout that renders a quick square matrix, and mobile Safari integrates URL sharing directly into the system Share Sheet. These browser-native features work well for ad-hoc personal transfers when sending a tab to your phone. However, when building a publishing engine, an interactive web application, or an automated static site pipeline, relying on manual browser menus falls short. You cannot customise the branding, you cannot automate social preview card generation in CI/CD, and you cannot serve dynamic QR codes programmatically to external consumers. Back in 2023, I explored this challenge in Generating QR codes - the Easy, the Comfy and the Smarty, comparing Google's deprecated Charts API, local scripting with the qrcode Python library and QRCoder NuGet package, and deploying serverless Google Cloud Functions. Three years later, our architecture has evolved significantly. Instead of managing external web services or deploying heavy runtimes for static content, we built clean, lightweight QR code engines across three distinct tiers: headless build-time matrix calculation in Python, client-side in-browser canvas rendering in JavaScript, and a high-performance Minimal API microservice in C# on .NET 10. Here is the engineering journey behind implementing the ISO/IEC 18004 standard, mastering Galois field mathematics, and compositing elegant frosted-glass badges. 1. Under the Hood: The Mathematics of ISO/IEC 18004 Invented in 1994 by Denso Wave, a QR (Quick Response) code is far more than a monochrome checkerboard. It is a mathematically resilient two-dimensional matrix engineered to survive severe optical distortion, tearing, and occlusion. Generating one requires four distinct mathematical and structural stages: flowchart LR A[\"Input Payload<br/><i>(Text or URL)</i>\"] --> B[\"Bitstream Encoding<br/><i>Mode + Count + Data</i>\"] B --> C[\"Reed-Solomon ECC<br/><i>Galois Field GF(256)</i>\"] C --> D[\"Matrix Construction<br/><i>Finders + Masking</i>\"] Galois Field \\(GF(256)\\) Arithmetic Standard integer arithmetic does not work for finite field error correction because division can yield non-integers. QR codes operate over the Galois Field \\(GF(2^8)\\) or \\(GF(256)\\), defined by the primitive polynomial: \\(P(x) = x^8 + x^4 + x^3 + x^2 + 1 \\quad (0x11D \\text{ or } 285)\\) In \\(GF(256)\\), addition and subtraction are identical to bitwise XOR (^). Multiplication is performed by expressing non-zero elements as powers of a primitive root \\(\\alpha=2\\). By precomputing log and exponent tables during startup, polynomial multiplication reduces to table lookups: # Galois Field GF(256) tables with primitive polynomial 0x11D (285) GF_EXP = [0] * 512 GF_LOG = [0] * 256 _x = 1 for _i in range(255): GF_EXP[_i] = _x GF_LOG[_x] = _i _x <<= 1 if _x >= 256: _x ^= 0x11D for _i in range(255, 512): GF_EXP[_i] = GF_EXP[_i - 255] def gf_mul(a: int, b: int) -> int: \"\"\"Multiplies two numbers in GF(256) using precomputed lookup tables.\"\"\" if a == 0 or b == 0: return 0 return GF_EXP[GF_LOG[a] + GF_LOG[b]] Reed-Solomon Error Correction (Level H) The ISO/IEC 18004 specification defines four Error Correction Code (ECC) levels: Level L: Recovers up to ~7% damaged data. Level M: Recovers up to ~15% damaged data. Level Q: Recovers up to ~25% damaged data. Level H: Recovers up to ~30% damaged data. Note To safely embed custom logos, site favicons, or brand emblems into the centre of a QR code without rendering the symbol unreadable, configuring Error Correction Level H is mandatory. Error correction codewords are computed using polynomial synthetic division. The data codewords represent coefficients of a message polynomial \\(M(x)\\), which is multiplied by \\(x^{num\\_ec}\\) and divided by a generator polynomial \\(G(x)\\): \\(G(x) = \\prod_{i=0}^{num\\_ec - 1} (x - \\alpha^i)\\) The remainder of this polynomial division represents the Reed-Solomon parity codewords appended directly after the data stream. Bitstream Assembly and Padding Before error correction, the payload string is encoded into an 8-bit byte mode bitstream: Mode Indicator: 4-bit header (0100 for 8-bit byte mode). Character Count Indicator: 8-bit (Versions 1 to 9) or 16-bit (Versions 10 to 14) binary integer specifying the payload length. Data Bits: Raw UTF-8 bytes. Terminator & Alignment: Up to 4 zero bits, padded to the nearest 8-bit byte boundary. Codeword Padding: Alternating bytes of 0xEC (236) and 0x11 (17) until the version data capacity is completely filled. Matrix Assembly and Masking Once data and parity blocks are interleaved, the matrix is constructed: Finder Patterns: \\(7 \\times 7\\) nested concentric squares placed at the top-left, top-right, and bottom-left corners, separated by a 1-module quiet zone. Alignment Patterns: \\(5 \\times 5\\) grids placed at deterministic coordinates for Version 2 and above. Timing Patterns: Alternating dark and light modules running horizontally and vertically along row 6 and column 6. Data Placement: Codewords placed in 2-column zig-zag upward and downward tracks, skipping reserved functional modules. Format Information: 15-bit sequence encoding the ECC level and mask pattern, protected by \\(BCH(15, 5)\\) error correction and XORed with the mask 0x5412. 2. The Centre Element: Use, Flexibility, and Constraints Embedding an icon, brand mark, or site favicon inside a QR code elevates a generic barcode into a polished visual asset. However, visual design must never compromise scanner decodability. Level H QR code with centred brand favicon and protective backing plate. Mathematical Safety Margin Because Level H error correction recovers up to 30% of obscured or corrupted codewords, an overlay occupying the geometric centre is interpreted by scanners as localised surface damage. To maintain robust scannability across low-quality smartphone cameras and angled lighting: The 24% Golden Rule: Restrict the centre badge diameter to at most 20% to 25% (ideally 24%) of the total QR code width and height. Surface Area Proportion: A badge scaled to 24% of the matrix width occupies only \\((0.24)^2 \\approx 5.76\\%\\) of the overall surface area. This leaves over 24% of Level H's recovery budget available for physical print wear, lens glare, or perspective skew. Clearance and Visual Contrast Never place a transparent PNG icon directly over the raw QR modules. High-frequency black modules showing through semi-transparent icon pixels confuse optical recognition algorithms. Two visual elements ensure 100% scanning reliability: Solid Backing Badge: Render a solid white (#ffffff) plate behind the logo, expanded with padding equal to 10% to 15% of the logo size. Soft Depth Shadow: Apply a subtle Gaussian drop shadow (radius: 3px, alpha: ~18%) behind the white badge. This cleanly separates the foreground emblem from the surrounding black modules. Optical Safety & Real-World Constraints The 4-Module Quiet Zone: The ISO standard mandates a clear margin of 4 empty modules around the matrix perimeter. While modern neural camera scanners tolerate narrower margins, maintaining adequate contrast around the edges is vital when embedding QR codes into decorative cards. Version Payload Thresholds: Using extension-less canonical URLs (e.g. https://jochen.kirstaetter.name/slug) keeps payloads within Version 3 to 5 (\\(29 \\times 29\\) to \\(37 \\times 37\\) modules), ensuring each data module remains large and sharp on mobile screens. Real-World Matrix Variations in Production To demonstrate how custom colourways and centre badges behave under Error Correction Level H, here are three real-world variations generated across our publishing pipeline: Variant Specifications Royal Indigo Raw Matrix Colour: Royal Indigo (#1e40af) on Slate-50 (#f8fafc) Contrast Ratio: 8.2:1 (WCAG AAA) Badge Configuration: Raw modules without centre emblem (100% data payload visibility; 0% occlusion). Target Article: Using portless with Firebase Hosting Optimised for high-density payloads, technical documentation sheets, and minimal print layouts. Deep Emerald & Favicon Colour: Deep Emerald (#065f46) on Pure White (#ffffff) Contrast Ratio: 7.5:1 (WCAG AAA) Badge Configuration: 24% centre brand favicon on a solid white plate with protective quiet margins. Target Article: Getting Started with SQL Server on GCP Tailored for database architecture, Google Cloud platform guides, and infrastructure articles. Warm Crimson & Favicon Colour: Warm Crimson (#9f1239) on Pure White (#ffffff) Contrast Ratio: 6.8:1 (WCAG AAA) Badge Configuration: 22% centre brand favicon with subtle background isolation. Target Article: Using Antigravity Remote Control Designed for multi-agent workflows, AI remote tooling, and systems architecture topics. 3. Accessibility and WCAG: Designing Inclusive QR Experiences QR codes are often viewed purely as marketing shortcuts or convenience tools for mobile camera users. When implemented thoughtfully, however, they serve as powerful bridges for assistive technology and digital accessibility (a11y). At the same time, visual 2D matrices introduce distinct user experience challenges that require strict adherence to the W3C Web Content Accessibility Guidelines (WCAG 2.2). Assistive Technology and Cognitive Benefits Cross-Device Assistive Bridge: Users reading technical articles or dense documentation on desktop screens frequently rely on mobile-specific accessibility tools, such as Apple VoiceOver, Android TalkBack, haptic feedback, spoken text, or handheld digital magnifiers. Scanning a QR code provides a convenient physical bridge to their primary assistive device without manual typing or email self-forwarding. Desktop Screen Reader Reality: For blind users operating desktop screen readers (e.g. NVDA or JAWS), pointing a physical phone camera at an unseen LCD screen is impractical without specialised tactile markers (such as NaviLens BidiCodes). On desktop viewports, accessibility is achieved not by the matrix itself, but by providing an immediate, selectable plain-text URL display (#qr-modal-url-display) with single-click keyboard copying. Eliminating Motor and Cognitive Strain: Manually typing long, hyphenated URLs or technical parameters is error-prone and exhausting for users with motor impairments, tremors, Parkinson's disease, dyslexia, or dyscalculia. A single physical camera scan bypasses keyboard input entirely. Physical-to-Digital Accessibility Transition: On physical conference slides, printed handouts, or hardware nameplates, QR codes allow low-vision attendees to transition to accessible web pages where font sizing, semantic screen reader headings, and custom high-contrast CSS can be applied. Security and \"Quishing\" Defence With the rise of QR phishing (Quishing), where malicious actors overlay deceitful barcodes or obfuscate redirect URLs, implementing transparent safeguards is essential: Canonical Extension-less URL Display: Always render the plain-text destination domain alongside the matrix so users can verify the HTTPS target before scanning. Same-Origin Asset Protection: Ensure embedded favicons and logos are served from trusted same-origin sources with strict CORS headers to prevent canvas tainting and visual spoofing. The GhostFx share modal rendering an explicit canonical URL directly beneath the matrix to verify target HTTPS destinations prior to scanning. The Developer's WCAG Compliance Checklist for QR Codes When incorporating QR codes into web interfaces, developers must observe four fundamental WCAG criteria: Non-Text Content Fallback (WCAG 1.1.1 - Level A): A QR code is an image and must never be rendered in isolation without meaningful alternative text. The DOM must provide a descriptive aria-label or alt text explicitly stating the destination (e.g. aria-label=\"Scan QR code to open article at https://jochen.kirstaetter.name/slug\"). Visible Plain Text URL: Always display the full target URL in selectable, copyable plain text alongside the graphic (as implemented in our modal with #qr-modal-url-display). Non-Text Contrast (WCAG 1.4.11 - Level AA): The contrast ratio between foreground QR modules and the background surface must exceed 3.0:1 for graphical user interface components, and ideally 7.0:1 (WCAG AAA) for enhanced optical and visual clarity. Keyboard Accessibility and Focus Management (WCAG 2.1.1 & 2.1.2 - Level A): Any button triggering a QR modal must be reachable and operable via keyboard (Tab, Enter, Space). The modal must trap focus while active, support Esc light-dismiss, and return focus to the triggering element upon closing. Target Size and Touch Comfort (WCAG 2.5.8 - Level AA): Interactive triggers for displaying QR codes, copying URLs, or initiating system shares must maintain a minimum touch target area of at least \\(24 \\times 24\\text{ px}\\) (\\(44 \\times 44\\text{ px}\\) for Level AAA mobile comfort). 4. Architectural Evaluation: Client-Side vs Microservice vs Static Build When architecting a solution, choosing where to generate QR codes involves clear trade-offs across latency, offline capabilities, infrastructure costs, and integration requirements. Explore each deployment architecture below: Client-Side (Browser JS) Serverless Microservice Headless Static Build flowchart LR Modal[\"Interactive Modal<br/><i>HTML5 &lt;dialog&gt;</i>\"] --> Canvas[\"Canvas 2D Renderer<br/><i>posts/ghostfx/public/js/qrcode.min.js</i>\"] Canvas --> Share[\"Web Share API<br/><i>navigator.share() / Clipboard</i>\"] Dimension Architectural Profile Response Latency Instant (\\(0\\text{ ms}\\) network latency) Offline Resilience Works fully offline via Service Worker URL Rewriting & Analytics Static to active page URL External Client Support Browser viewport only Hosting Cost $0.00 (Client-side execution) Privacy & Security 100% private (URL never leaves client) When to Choose: Best for web applications and blogs where users share the active page URL directly to a mobile device. It incurs zero cloud hosting costs, executes instantly with zero network latency, and functions when users are completely offline. flowchart LR API[\"ASP.NET Core Minimal API<br/><i>GET /api/qr?url=...</i>\"] --> Cache[\"In-Memory Logo Cache<br/><i>IBrandedLogoCache Singleton</i>\"] Cache --> Skia[\"SkiaSharp Compositor<br/><i>24% Badge + Level H ECC</i>\"] Skia --> Response[\"HTTP Response<br/><i>ETag + immutable</i>\"] Dimension Architectural Profile Response Latency \\(50\\text{ ms} - 250\\text{ ms}\\) HTTP roundtrip Offline Resilience Requires active internet connection URL Rewriting & Analytics Dynamic URL rewriting, click tracking, shortlinks External Client Support Google Sheets (=IMAGE()), transactional emails, PDFs, thermal printers Hosting Cost Pay-per-invocation serverless compute Privacy & Security Server logs incoming query strings When to Choose: Highly appealing whenever you need flexible URL parameter handling, dynamic shortlinks, server-side click tracking, or direct integration with third-party consumers like spreadsheets, email dispatchers, and IoT receipt printers. flowchart LR PreBuild[\"Pre-Build Automation<br/><i>scripts/localize-assets.py</i>\"] --> MatrixPy[\"Matrix Engine<br/><i>scripts/qr_generator.py</i>\"] MatrixPy --> OGCard[\"Open Graph Cards (1200x630)<br/><i>42% Frosted Glass Plate</i>\"] Dimension Architectural Profile Response Latency Pre-rendered (\\(0\\text{ ms}\\) runtime) Offline Resilience Statically cached and CDN distributed URL Rewriting & Analytics Baked into compiled HTML External Client Support Social metadata cards (Open Graph previews) Hosting Cost $0.00 (CI/CD build pipeline) Privacy & Security Internal pipeline only When to Choose: Ideal for static site generation (SSG) pipelines where high-resolution social preview cards must be generated deterministically before deployment. 5. Implementation Across Three Tiers To see how these concepts translate into real code, explore the implementations across three pragmatic deployment tiers: lightweight Python scripts, an interactive canvas renderer in JavaScript, and a high-throughput ASP.NET Core Minimal API on .NET 10: Python Python (self) JavaScript C# (.NET 10) # --------------------------------------------------------------------------- # The Pragmatic Approach: Standard Library Scripting # --------------------------------------------------------------------------- # pip install qrcode[pil] import qrcode from PIL import Image def generate_qr_simple(url: str, logo_path: str = \"favicon.png\", output_file: str = \"qrcode.png\"): \"\"\"Pragmatic QR generator using standard tooling with Level H ECC and logo badge.\"\"\" qr = qrcode.QRCode( version=None, # Automatically determines smallest version accommodating payload error_correction=qrcode.constants.ERROR_CORRECT_H, box_size=10, border=4, ) qr.add_data(url) qr.make(fit=True) img = qr.make_image(fill_color=\"#111827\", back_color=\"#ffffff\").convert(\"RGBA\") # Overlay centre logo adhering to the 24% Golden Rule if logo_path: logo = Image.open(logo_path).convert(\"RGBA\") logo_size = int(img.size[0] * 0.24) logo = logo.resize((logo_size, logo_size), Image.Resampling.LANCZOS) pos = ((img.size[0] - logo_size) // 2, (img.size[1] - logo_size) // 2) img.paste(logo, pos, mask=logo) img.save(output_file) # --------------------------------------------------------------------------- # The Algorithmic Engine: Self-Contained Matrix & Headless Compositor # --------------------------------------------------------------------------- # scripts/qr_generator.py - Self-contained ISO/IEC 18004 Galois Field matrix generator from pathlib import Path from typing import List, Tuple, Optional from PIL import Image, ImageDraw, ImageFilter # Galois Field GF(256) Arithmetic & Synthetic Polynomial Division def gf_mul(a: int, b: int) -> int: return 0 if (a == 0 or b == 0) else GF_EXP[GF_LOG[a] + GF_LOG[b]] def poly_mul(p1: List[int], p2: List[int]) -> List[int]: res = [0] * (len(p1) + len(p2) - 1) for i, c1 in enumerate(p1): for j, c2 in enumerate(p2): res[i + j] ^= gf_mul(c1, c2) return res def rs_encode(data: List[int], num_ec: int) -> List[int]: \"\"\"Computes Reed-Solomon parity codewords via synthetic polynomial division.\"\"\" gen = [1] for i in range(num_ec): gen = poly_mul(gen, [1, GF_EXP[i]]) msg = list(data) + [0] * num_ec for i in range(len(data)): lead = msg[i] if lead != 0: for j in range(len(gen)): msg[i + j] ^= gf_mul(gen[j], lead) return msg[len(data):] # Headless Pillow Compositor for Open Graph Cards (<slug>-og.webp) def generate_qr_image( text: str, box_size: int = 4, border: int = 2, fg_color: Tuple[int, int, int, int] = (17, 24, 39, 255), bg_color: Tuple[int, int, int, int] = (255, 255, 255, 255), logo_path: Optional[str] = None, logo_size_ratio: float = 0.24 ) -> Image.Image: \"\"\"Renders a PIL RGBA image of the QR Code with Level H error correction and centre badge.\"\"\" matrix = generate_qr_matrix(text) # Level H pure Python matrix engine size = len(matrix) img_size = (size + border * 2) * box_size img = Image.new(\"RGBA\", (img_size, img_size), bg_color) pixels = img.load() # Draw QR code modules for r in range(size): for c in range(size): if matrix[r][c] == 1: x_start = (c + border) * box_size y_start = (r + border) * box_size for dy in range(box_size): for dx in range(box_size): pixels[x_start + dx, y_start + dy] = fg_color # Embed centre badge with soft shadow if logo_path and Path(logo_path).exists(): logo = Image.open(logo_path).convert(\"RGBA\") l_size = int(img_size * logo_size_ratio) lx = (img_size - l_size) // 2 ly = (img_size - l_size) // 2 pad = max(3, int(l_size * 0.12)) # White rounded backing plate badge = Image.new(\"RGBA\", (img_size, img_size), (0, 0, 0, 0)) b_draw = ImageDraw.Draw(badge) b_draw.rounded_rectangle( [(lx - pad, ly - pad), (lx + l_size + pad, ly + l_size + pad)], radius=int(pad * 1.5), fill=(255, 255, 255, 255), outline=(226, 232, 240, 255), width=1 ) # Soft shadow b_shadow = Image.new(\"RGBA\", (img_size, img_size), (0, 0, 0, 0)) bs_draw = ImageDraw.Draw(b_shadow) bs_draw.rounded_rectangle( [(lx - pad + 1, ly - pad + 2), (lx + l_size + pad + 1, ly + l_size + pad + 3)], radius=int(pad * 1.5), fill=(0, 0, 0, 45) ) b_shadow = b_shadow.filter(ImageFilter.GaussianBlur(radius=3)) img = Image.alpha_composite(img, b_shadow) img = Image.alpha_composite(img, badge) resized_logo = logo.resize((l_size, l_size), Image.Resampling.LANCZOS) img.paste(resized_logo, (lx, ly), resized_logo) return img Source reference: scripts/qr_generator.py#L330-L392 // posts/ghostfx/public/js/qrcode.min.js & qr-modal.tmpl.partial // Client-side HTML5 Canvas QR rendering with logo badge overlay & Web Share function renderQrCodeToCanvas(containerElement, options) { const text = options.text; const size = options.width || 180; const logoUrl = options.logoUrl || '/favicon.png'; const logoRatio = options.logoSize || 0.24; const qr = new QRCodeModel(-1, QRErrorCorrectLevel.H); qr.addData(text); qr.make(); const canvas = document.createElement('canvas'); canvas.width = size; canvas.height = size; canvas.setAttribute('role', 'img'); canvas.setAttribute('aria-label', `QR Code linking to ${text}`); const ctx = canvas.getContext('2d', { willReadFrequently: false }); const moduleCount = qr.getModuleCount(); const tileW = size / moduleCount; const tileH = size / moduleCount; // Render modules for (let row = 0; row < moduleCount; row++) { for (let col = 0; col < moduleCount; col++) { ctx.fillStyle = qr.isDark(row, col) ? '#111827' : '#ffffff'; ctx.fillRect( Math.round(col * tileW), Math.round(row * tileH), Math.ceil(tileW), Math.ceil(tileH) ); } } // Embed centre favicon badge if (logoUrl) { const img = new Image(); img.crossOrigin = 'anonymous'; // Prevents canvas tainting img.onload = () => { const lSize = size * logoRatio; const lx = (size - lSize) / 2; const ly = (size - lSize) / 2; const pad = Math.max(3, lSize * 0.12); // Draw white rounded plate using Canvas roundRect() API ctx.save(); ctx.fillStyle = '#ffffff'; ctx.shadowColor = 'rgba(0, 0, 0, 0.18)'; ctx.shadowBlur = 6; ctx.shadowOffsetY = 2; ctx.beginPath(); ctx.roundRect(lx - pad, ly - pad, lSize + pad * 2, lSize + pad * 2, pad * 1.2); ctx.fill(); // Subtle border ctx.shadowColor = 'transparent'; ctx.lineWidth = 1; ctx.strokeStyle = '#e2e8f0'; ctx.stroke(); // Draw logo ctx.drawImage(img, lx, ly, lSize, lSize); ctx.restore(); }; img.src = logoUrl; } containerElement.replaceChildren(canvas); } Source reference: posts/ghostfx/partials/qr-modal.tmpl.partial#L53-L123 // Program.cs - Production-Grade ASP.NET Core Minimal API Microservice // Target: .NET 10 (C# 14 records & spans; forward-compatible with .NET 11) // Packages: QRCoder (>= 1.6.0), SkiaSharp (>= 3.116.1) using System.Security.Cryptography; using Microsoft.AspNetCore.Http.HttpResults; using QRCoder; using SkiaSharp; var builder = WebApplication.CreateBuilder(args); // Pre-cache logo asset as singleton to guarantee zero disk I/O on hot path builder.Services.AddSingleton<IBrandedLogoCache>(sp => { var env = sp.GetRequiredService<IWebHostEnvironment>(); var logoPath = Path.Combine(env.WebRootPath ?? AppContext.BaseDirectory, \"favicon.png\"); return new BrandedLogoCache(logoPath); }); var app = builder.Build(); app.MapGet(\"/api/qr\", Results<FileContentHttpResult, BadRequest<string>> ( string url, int? size, bool? badge, IBrandedLogoCache logoCache, HttpContext context) => { if (string.IsNullOrWhiteSpace(url) || !Uri.TryCreate(url, UriKind.Absolute, out _)) { return TypedResults.BadRequest(\"A valid absolute URI is required.\"); } int pixelsPerModule = Math.Clamp(size ?? 12, 4, 40); bool embedBadge = badge ?? true; // 1. Generate QR Code Matrix with Level H Error Correction using var qrGenerator = new QRCodeGenerator(); using var qrData = qrGenerator.CreateQrCode(url, QRCodeGenerator.ECCLevel.H); using var qrCode = new PngByteQRCode(qrData); byte[] baseQrBytes = qrCode.GetGraphic(pixelsPerModule); byte[] finalPngBytes; if (embedBadge && logoCache.HasLogo) { // 2. Composite Centre Logo Badge using SkiaSharp using var baseBitmap = SKBitmap.Decode(baseQrBytes); using var canvas = new SKCanvas(baseBitmap); float width = baseBitmap.Width; float logoSize = width * 0.24f; // 24% Golden Rule for Level H ECC float lx = (width - logoSize) / 2f; float ly = (width - logoSize) / 2f; float pad = Math.Max(4f, logoSize * 0.12f); var badgeRect = new SKRoundRect( new SKRect(lx - pad, ly - pad, lx + logoSize + pad, ly + logoSize + pad), pad * 1.5f ); // Soft drop shadow using var shadowPaint = new SKPaint { Color = new SKColor(0, 0, 0, 45), MaskFilter = SKMaskFilter.CreateBlur(SKBlurStyle.Normal, 3f), IsAntialias = true }; canvas.DrawRoundRect(badgeRect, shadowPaint); // Solid white protective plate with 1px border using var platePaint = new SKPaint { Color = SKColors.White, IsAntialias = true }; using var borderPaint = new SKPaint { Color = new SKColor(226, 232, 240), Style = SKPaintStyle.Stroke, StrokeWidth = 1f, IsAntialias = true }; canvas.DrawRoundRect(badgeRect, platePaint); canvas.DrawRoundRect(badgeRect, borderPaint); // Draw in-memory cached logo bitmap var destRect = new SKRect(lx, ly, lx + logoSize, ly + logoSize); using var sampling = new SKSamplingOptions(SKFilterMode.Linear, SKMipmapMode.Linear); canvas.DrawImage(logoCache.LogoImage, destRect, sampling); using var surfaceImage = SKImage.FromBitmap(baseBitmap); using var encodedData = surfaceImage.Encode(SKEncodedImageFormat.Png, 100); finalPngBytes = encodedData.ToArray(); } else { finalPngBytes = baseQrBytes; } // 3. Apply HTTP caching headers and deterministic ETag string etagHash = Convert.ToHexString(SHA256.HashData(finalPngBytes))[..16]; context.Response.Headers.ETag = $\"\\\"{etagHash}\\\"\"; context.Response.Headers.CacheControl = \"public, max-age=604800, immutable\"; return TypedResults.Bytes(finalPngBytes, \"image/png\"); }); app.Run(); public interface IBrandedLogoCache { bool HasLogo { get; } SKImage? LogoImage { get; } } public sealed class BrandedLogoCache : IBrandedLogoCache, IDisposable { public bool HasLogo => LogoImage is not null; public SKImage? LogoImage { get; } public BrandedLogoCache(string filePath) { if (File.Exists(filePath)) { using var rawBitmap = SKBitmap.Decode(filePath); if (rawBitmap is not null) { LogoImage = SKImage.FromBitmap(rawBitmap); } } } public void Dispose() => LogoImage?.Dispose(); } 6. Real-World GhostFx Integrations In ghostfx, we combine both client-side and build-time zero-dependency QR engines to deliver a seamless publishing workflow. Note GhostFx vs. ghostfx: GhostFx refers to the open-source static site converter project designed to bridge Ghost themes with DocFX. ghostfx (lowercase code formatting) designates the local DocFX template directory and theme asset bundle (posts/ghostfx/) powering this blog's layouts, partials, and interactive modals. 1. The Interactive Share Modal Visitors clicking the QR icon in the article header trigger an HTML5 <dialog> component powered by posts/ghostfx/public/js/qrcode.min.js. <dialog id=\"qr-code-dialog\" class=\"media-lightbox qr-dialog\" closedby=\"any\" aria-label=\"Share and QR Code\"> <div class=\"media-lightbox-content qr-dialog-content\"> <div class=\"qr-modal-container\"> <div class=\"qr-modal-header\"> <span class=\"qr-modal-badge\">QR Code &amp; Share</span> <h3 class=\"qr-modal-title\" id=\"qr-modal-article-title\">Article Title</h3> </div> <div class=\"qr-modal-body\"> <div id=\"qr-code-graphic\" class=\"qr-code-graphic\"></div> <div id=\"qr-modal-url-display\" class=\"qr-url-text\"></div> </div> </div> </div> </dialog> The script automatically strips .html extensions to produce clean, extension-less canonical URLs (e.g. https://jochen.kirstaetter.name/mastering-the-matrix-qr-code-generation), renders the QR code with the brand favicon, and connects to the Web Share API (navigator.share()) on mobile devices. 2. The Automated Open Graph Social Card Pipeline During site compilation, scripts/localize-assets.py invokes our pure Python generator scripts/qr_generator.py to build \\(1200 \\times 630\\text{ px}\\) Open Graph social cards (<slug>-og.webp). The pipeline composites: A depth-blurred hero image backdrop. A 42% translucent frosted-glass title plate with balanced typography. An author attribution plate with domain metadata. A crisp, scannable QR code card in the bottom-right corner linking directly to the extension-less post URL. # scripts/localize-assets.py clean_app_url = app_url.rstrip('/') post_url = f'{clean_app_url}/{slug}' # Extension-less URL favicon_path = str(POSTS_DIR / \"favicon.png\") qr_img = generate_qr_image( post_url, box_size=4, border=1, fg_color=(17, 24, 39, 255), bg_color=(255, 255, 255, 255), logo_path=favicon_path, logo_size_ratio=0.24 ) 7. Key Takeaways and Editorial Summary Pragmatic Architecture over Dogma: Balance zero-dependency algorithmic purity with battle-tested community tooling. In Python, the standard qrcode library (qrcode[pil]) delivers immediate, robust results for rapid scripting, whereas custom Galois field matrix engines excel in zero-dependency build-time automation pipelines. In .NET, pairing QRCoder with SkiaSharp on ASP.NET Core yields enterprise-grade microservice throughput, in-memory caching, and cross-platform compositing without reinventing matrix mathematics. Level H is Mandatory for Centre Badges: Always configure Error Correction Level H when overlaying logos, and cap the badge dimensions at 24% of the total matrix width. Prioritise Inclusivity with WCAG: Provide descriptive aria-label text, selectable plain-text URLs, keyboard-accessible modals, and high-contrast colourways. Select the Right Execution Tier: Pair client-side generation for zero-latency user sharing with headless build-time automation for social media card generation. What approaches have you taken when integrating QR codes into your web apps or static site generators? Connect and share your thoughts on X (@JKirstaetter), Bluesky (@jochen.kirstaetter.name), Mastodon (@JKirstaetter), or subscribe via our RSS feed. Acknowledgements and References: Denso Wave (ISO/IEC 18004), Kazuhiko Arase (qrcode.js), and Thonky's QR Code Specification Guide. Picture credits: Gemini 3.1 Flash Image - \"An artistic, high-tech isometric illustration of glowing mathematical Galois field formulas transforming into sleek, scannable QR codes with frosted glass layers and embedded brand icons in a modern developer workspace aesthetic.\" if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mastering-the-matrix-qr-code-generation.html'; this.page.identifier = '9271ed1c97_idmastering-the-matrix-qr-code-generation'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mau-pas-konpran-2.html": {
    "href": "mau-pas-konpran-2.html",
    "title": "Mau Pas Konpran!? Brain Drain - Get Blogged by JoKi",
    "summary": "Mau Pas Konpran!? Brain Drain Copy Markdown View Markdown GitHub Markdown Source Mau Pas Konpran!? Brain Drain Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mau-pas-konpran-2'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mau-pas-konpran-2'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Mau Pas Konpran!? Brain Drain') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-12-19 - Filed under PodcastPersonalCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); I don't understand!? - In our latest episode we - Loic, Ish and I - sat together and talked about the situation, possible reasons, and considerations regarding the emigration of Mauritians towards other countries and the potential impact on the society here on the island. The so-called \"brain drain\" is happening since years and it kind of amazes me to see that people are looking for their chances and luck abroad when there are opportunities available in the country. \"The Grass is always greener on the other side.\" This proverb has the general understanding that life at other places is better than the current situation you are in. However this might be a superficial observation and there are other constraints playing into that, too. Personally, I did the reverse. Meaning, I immigrated to Mauritius almost 17 years ago; initially as an expat on an Occupation Permit. And I have to admit that reaching certain goals and achievements would not have been possible back in Germany. Maybe possible but might have required much more effort, more time and more personal involvement. Who knows... Mauritius and its brain drain - why is talent moving away? Please, check out episode #2 of the Mau Pas Konpran podcast and let us know your thoughts on this particular topic. Maybe share your own experience in case you have moved away from your home country, and how you see it nowadays. Thanks for reading and hopefully listening. Joyeuses fêtes et BananE! PS: We are considering to publish the podcast on Apple platform. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mau-pas-konpran-2.html'; this.page.identifier = '9271ed1c97_idmau-pas-konpran-2'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mau-pas-konpran.html": {
    "href": "mau-pas-konpran.html",
    "title": "Mau pas konpran!? SIM Registration - Get Blogged by JoKi",
    "summary": "Mau pas konpran!? SIM Registration Copy Markdown View Markdown GitHub Markdown Source Mau pas konpran!? SIM Registration Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mau-pas-konpran'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mau-pas-konpran'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Mau pas konpran!? SIM Registration') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-11-19 - Filed under PodcastPersonalCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); I don't understand!? - Exactly this is how I feel quite often reading through the large amount of technological news and announcements every day. In this podcast we are going to pick up various topics that happened recently and going to shed some light on them with our points of view and experiences. The podcast's hosts are Loïc Forget, Ish Sookun and myself. I invite you, my dear readers, to embark with us onto this new journey. Let's see where the winds and tides of the internet are going to carry us. I'm excited for this adventure and it finally opens the world of podcasts for me. Wish us good fortune and drop us your remarks - the good, the bad and the ugly ones. As we move from episode to episode we are sure to learn a heap and to improve. So, please bear with us, and give us your honest feedback straight from the heart. Following teaser for the show has been written. We’re launching our first podcast. \uD83C\uDF99️ Email in your technical questions so we can reply to them during our first episode this weekend. #MauPasKonpran \uD83D\uDCE7 Dear reader, This Sunday the 19th of November, we will be launching the first podcast on the l’Express Spotify account entitled ‘Mau pas konpran !?’. This monthly program will include a select local & international technological news. We are especially excited to read the most challenging of YOUR technical questions. Those can be any questions relating to technical issues you are having with a website, an app, your mobile, your smartwatch or your computer etc etc This first episode will include Jochen Kirstätter, freelance senior software crafter, Ish Sookun, systems architect at La Sentinelle. The two organize the annual #DevConMU Developers Conference in Mauritius since 2015 and of course myself, Loïc Forget, business development manager at La Sentinelle. We will then select the best & most challenging questions and attempt to answer them during the program. We invite you to send your questions to the following email address podcast@lexpress.mu Best of luck & looking forward to reading your perplexing questions. Loïc Read the article on L'Express and send us your technology-related questions. I hope it connects with you and I'm looking forward to tons of interesting questions and inspiring experiences. The initial release of \"Mau pas konpran!?\" is on Spotify. More podcast platforms might follow in the future. Stay tuned! Please let us know what you think about the show and how we could improve it further. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mau-pas-konpran.html'; this.page.identifier = '9271ed1c97_idmau-pas-konpran'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mauritius-open-data.html": {
    "href": "mauritius-open-data.html",
    "title": "Mauritius Open Data Readiness Assessment (ODRA) - Get Blogged by JoKi",
    "summary": "Mauritius Open Data Readiness Assessment (ODRA) Copy Markdown View Markdown GitHub Markdown Source Mauritius Open Data Readiness Assessment (ODRA) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mauritius-open-data'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mauritius-open-data'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Mauritius Open Data Readiness Assessment (ODRA)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-11-07 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Although Open Data is around since several years in other countries and has been initiated in Mauritius already back in 2012, it is only this year that there seems to be more momentum towards an Open Data initiative. Back in May 2015 I was kindly contacted by Alla Morrison, Program Officer at the World Bank, in regards of showing genuine interest in open government data for members of the Mauritius Software Craftsmanship Community (MSCC). Initial information and workshop on Open Data As founder of a local IT community and representative of more than 250 software craftsmen I was positively surprised by this, and the message was circulated immediately. During the second week of June, the Open Data team of the World Bank held several public sessions. Actually, I managed to attend two sessions relevant for developers. The first event was conducted at the Prime Ministers Office in Port Louis and focused mainly on the ideas, concepts and benefits of Open Data in general. The given use cases and success stories around Open Data were impressive, and it was very interesting to see that solid solutions can be provided by anyone interested to solve a specific problem. Attendees of various IT user groups and communities in Mauritius at the workshop with the World Bank on Open Data in Mauritius Delegation of the World Bank during the workshop with local IT user groups During the second get-together, which was more like a workshop, the team wanted to know exactly what kind of open government data and datasets would be of interest for IT folks here on the island. Based on our requests and the talks to the various ministries in Mauritius the team at the World Bank conducted their Open Data Readiness Assessment (ODRA). Open Data Readiness Assessment - Findings Fast-forward to end of October, the Open Data team of the World Bank completed their assessment, and members of the MSCC and other organisations were again invited to receive information about the findings and the suggestions first-hand. This time the event was held at the Rajiv Gandhi Science Centre in Bell Village, and surprisingly there were fewer IT people this time. Nonetheless, it felt a bit like a press conference and taking notes as well as pictures during the various presentation had a touch of journalism... Even though I'm quite late on blogging about the topics I got a lot of answers to my questions and the general outcome of the assessment for Mauritius is positive. Surely, there are areas of improvement but overall it looks very promising for us developers to get our hands on open government data soon. Other attendees like Ish and SM published their thoughts already earlier, so I won't repeat myself with those details but just give you a brief summary on the topics I'd be most interested in within the next couple of months. Frankly, here is what I asked Alla upfront via email: \"I started with the preparations of the Developers Conference 2016 - https://2016.mscc.mu/ - recently, and I'd like to see whether it would be possible to have access to any datasets of Open Data in April/May next year in order to schedule a hackathon or app challenge during the conference days.\" And the signs within the government of Mauritius are looking good based on the findings of the World Bank and seeing the increased commitment of the Ministry of Technology, Communication and Innovation (MTCI) makes room for a solid corporation and platform of exchange. Currently, there are 15 data-sets of Open Data already available from the Mauritian Government in a machine-readable format and properly licensed. An Open Data committee will be in place soon. Various activities to provide and promote Open Data are already planned. Explanation of findings in the Open Data Readiness Assessment and Q&A session In regards to provide access to open government data the World Bank has implemented a 5* rating of Open Data Formats. The Mauritian Government is on a firm way to a 3* rating as some datasets are already available in machine-readable, neutral open formats: CSV, XML, JSON, etc. First, publish \"as-is\" and then engage with the dataset users in order to improve the quality of information and optional the format(s) over time. Later on, adapt international-recognised data exchange formats based on the domain of information. Advantages named by the World Bank General public access and free available data sets of open government data carry these attributes forward: Economic value: new business opportunity Transparency / accountability: easy to analyse and reproducible reports Data exchange across government: simplified access across governemnt departments Data-informend policy making: decision are made based on applicable, relevant data The team of the World Bank also reported about GDP improvements in other countries between 0.4% and up to 4% per annum by giving people access to Open Data. ODRA findings Mauritius is already well placed to implement an Open Data initiative. The government has commitment to provide open data and there is a strong demand by developers, private sector and researchers. Technically, the government already has a good fundament to publish statistical data in an open data format Following are the findings in the 8 pillars of the ODRA Senior leadership: green/yellow Policy Framework: yellow Concerns based on the activities of DPO since 2004 but no Freedom of Information (FOI) act. No clear information on policies and licensing of data. Cost of printed data and no clear handling of data release. Lack of clear guidance by the government. Institutional Structures & Capabilities: green Data Management Policies and Procedures: yellow Lack of a comprehensive inventory of governmental data across the ministries. Statistics Mauritius has real capabilites and data management (also requested additional training) Demand for Open Data: green/yellow Over 300 people have requests to develop apps using Open Data. Strong expressed data demand. \uD83D\uDE09 Difficult to get access to Open Data from the government, ministries and agencies. Lack of engagement between the various entities. Improved interaction and active engagement by the ministries towards the user groups. Civic Engagement & Capabilities: yellow Universities and colleges offer programs for ICT inclusive Big Data Absence of data journalism; no social media engagement Apps economy is at an early stage Finance: green/yellow Although there are strategy plans to include Open Data there is no concrete, specific funding allocation of money for Open Data in place yet. The Smart Mauritius budget should cover those aspects and the government should consider to move towards using open Source Software. National Technology and Skills Infrastructure: yellow Even though there is a technology infrastructure and high mobile penetration but with high broadband tariffs and ICT skills not meeting the market needs the findings have some reduction. Implementation of Open Data in Mauritius Working towards an Open Data Portal could / should be based on leadership by the MTCI; each ministry should opt for an \"implementation cell\" working closely with a Chief Data Officer (CDO) and the users of Open Data. The development of detailed policies \"open by default\" and exceptions, licensing, changes to charges and schedules is inevitable. Clear strategies for audience growth and users engagement are recommended within a short period. Also, the assessment suggests a release of so-called \"quick win\" datasets onto the existing portal early and the stimulation of hackathons or app challenges organised together with local user groups. Datasets that could be made open data quickly by the government of Mauritius Those datasets could be ready in a couple of months and usable for any kind of coding challenges and hackathons. Crossing fingers that it will be. Of course, there are more datasets of interest and it will be our responsibility to ask for such information in a healthy dialog with the corresponding public bodies, mainly the Ministry of TCI. The findings of the ODRA suggests to the government that any kind of data should be \"Open by default\" including clear definition of restrictions to restricted and sensitive data. Obviously, personalised information have to be anonymised by the ministries prior to grant general access. The necessary competence does already exist within the Ministry of TCI according to the commitee of the World Bank. Additional training among ministries could be conducted and a general guideline for all institutions could be defined, too. The Open Data Readiness Assessment differentiates between access to information and access to Open Data The recommended license for Open Data is based on international best practice: Creative Commons Attribution 4.0 Get free access to data, be able to share it freely, just give proper attribution of the source of the dataset. Existing data is to be included in the Open Data initiative even though it might have been \"published\" previously under closed data formats or inconvenient licensing. Sounds pretty good actually. Let your users become your advocates... clear recommendation by the World Bank towards the Mauritian Government to provide open data and datasets in the right format(s) and to clarify the various demands by the private sector Strong encouragement towards hackathons and pro-active advertisement/notification of the OD portal and activities on OD datasets. Various ways of capacity building: Internships in the government data journalism bootcamps data skills on all levels of the eductional system (primary, secondary and tertiary) Big data concepts in curriculum of universities Resources for OD users: School of Data Check the Open Government Data Kit of the World Bank TED talks Samples of active Open Data user groups: Data{Meet} Open Data Labs Cafe de Data OpenStreetMap Code for Africa - a 'federated' umbrella organisation. @Code4Africa on Twitter Data Liberation: \"Scrape-a-thons\" to collect and provide more data Data Fellowships Data Skills Data Tools What about \"Code for Mauritius\"? Idea for a new community around Open Data? Outlook for Mauritius and MSCC The findings of the readiness assessment of the World Bank were informative but brief. The full report is with the government and will be presented to the Cabinet during the next couple of weeks. Hopefully it will be published publicly in the near future. As for the MSCC, I had several conversations with key persons at the Ministry of TCI and the National Computer Board about ways of how Open Government Data could be helpful for future activities of the MSCC and how we can improve the dialog with public bodies in regards of more transparency and nourishment of local IT talent. The samples of success story from other countries were really inspiring and I'm very confident that similar results can be produced here in Mauritius. Given that data-sets will be available for free and under the right license anything is possible. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mauritius-open-data.html'; this.page.identifier = '9271ed1c97_idmauritius-open-data'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mauritius-vibrations.html": {
    "href": "mauritius-vibrations.html",
    "title": "App Review: Mauritius Vibrations - Get Blogged by JoKi",
    "summary": "App Review: Mauritius Vibrations Copy Markdown View Markdown GitHub Markdown Source App Review: Mauritius Vibrations Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mauritius-vibrations'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mauritius-vibrations'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'App Review: Mauritius Vibrations') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-03-05 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Finally, someone was able to realize one of 'my' ideas for an app. Since I'm here on the island I was always kind of annoyed by the simple fact that you could only read the articles about events that had happened already. Any kind of advertisement or pre-event information was and still is hardly available for quite a number of good events here in Mauritius. Event calendar and details in Mauritius Luckily, the development team at Knowledge Seven in Quatre Bornes took care of this issue and they recently published their Android app \"Mauritius Vibrations\" on the Play Store. Mauritius Vibrations: Overview of events Mauritius Vibrations: Event details The app is available in two editions: Mauritius Vibrations Lite (free but ad-supported) Mauritius Vibrations ($ 3.01 price tag) As soon as I saw the public posting over on Google+ I went to their official announcement on the Knowledge Seven blog to get some more background details. So, shut up and take my money \uD83D\uDE09 Here's my current app review I posted on the Play Store: Good start, needs some polishing Well done, and highly appreciated events information. After a quick look I would like to see more views, like 'This week' or 'This month'. Some entries, like movies at the cinema seem to be in the wrong category, ie. child-friendly. Furthermore, I wonder whether events can be classified by multiple categories? It's a solid start but the app needs more polishing and a couple of improvements on the UI. Of course, it will take a couple of iterations to get the app running smoothly and to the likes of the users, but it's already a great start and I like the UI and handling. I'll be glad to update my app review soon... And looking forward to see an iOS, a Windows Phone 8, as well as a Windows 8 app. Keep the good work up and deploy fast and often! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mauritius-vibrations.html'; this.page.identifier = '9271ed1c97_idmauritius-vibrations'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mb-nightmare-at-bagatelle.html": {
    "href": "mb-nightmare-at-bagatelle.html",
    "title": "Mugg & Bean - Nightmare at Bagatelle - Get Blogged by JoKi",
    "summary": "Mugg & Bean - Nightmare at Bagatelle Copy Markdown View Markdown GitHub Markdown Source Mugg & Bean - Nightmare at Bagatelle Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mb-nightmare-at-bagatelle'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mb-nightmare-at-bagatelle'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Mugg & Bean - Nightmare at Bagatelle') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-05-04 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It took me quite some while to decide whether I should write about this culinary topic or not. In general, I try to keep it like Thumper - \"If you can't say something nice, don't say nothin' at all...\" but unfortunately this won't change the situation in the first place and second, it's not recommended to hold back (constructive) criticism in order to assist to improve things. In case of Mugg & Bean at Bagatelle particularly it's already since a very long time that I'm saying nothin'... Bright and shiny opening Mugg & Bean isn't new to myself at all. I've been to M&B in South Africa several times and having the news that there'll be a franchise in Mauritius was very welcoming. Even though I skipped the opening period on purpose for various reasons I have to admit that the initial service, offerings and gusto was to my personal liking. Surely, a place to recommend among friends and family. We have been there on several occassions and the overall experience right at the beginning was positive. You know, this typical feel-good feeling after having had a nice meal and pleasant waitron service. Where's light, there're shades Unfortunately, M&B's lucky strike didn't last for long and personally I had the impression that the decline started as soon as the initial management and trainers from South Africa left the building, or better said flew back. The first disappointing visit happened for a lunch break together with my BWE and my dear mother. Well, you know lunch break, you'd like to go out and instead of grabbing a sandwich from the super market or picking some snacks from the merchants on the street, we decided to stop by at M&B to have a quick bite. Hm, talking a quick... it was only our patience that went away quickly. First of all, in an half-filled restaurant with many empty tables left we were seated but not asked for drinks. Finally, after roughly 20 minutes someone of the waitron staff remembered that new guests have arrived and brought us menu cards and asked for the drinks. It's not like we were hungry and eventually might have limited time for a lunch break but okay at least we a waitress now. The drinks were brought after another 20 minutes in, and then we were finally allowed to place our orders for food. Seriously? Round about 40 minutes just sitting there and waiting for soft drinks and/or juices? And obviously, we were not the only ones. A couple two tables next to us had to wait approximately 30 minutes for their drinks. Alright, as we were sitting nicely and had some topics to talk about we waited patiently for our dishes... Oh boy, you might have guessed it. Another 35 minutes later the first two plates were brought over, and then another 5 minutes later the missing third one. I won't go into the details but there were bits and pieces wrong and we couldn't be bothered because of being too hungry to argue. Besides having a second, more weird waiter which seemed to have a personal pleasure in terms of sneaking up on guests from behind repetitively, all three of us had probably the worst lunch ever. And to close that horrible lunch \"experience\" we had to wait another 25(!) minutes between asking for the check and finally being allowed to pay. Regular location for meetings of MSCC and LUGM Well, surely this lunch event must have been an exceptional experience you'd might argue. Hm, I wished it would have been... Let's talk a little bit about my involvement in local IT communities, namely Mauritius Software Craftsmanship Community (MSCC) and the Linux User Group of Mauritius/Meta (LUGM). We have this habit to organise little get-togethers of six to ten people and meet at an easy accessible location. Frankly, some love the bottomless caffee at Mugg & Bean and therefore we arranged quite a number of meetings over there. As the kids are with me on Saturdays I have to take care of their well-being and nutrition. Okay, the food selection even for children is quite tempting and after all it's about having a good time. Despite the business generated for the restaurant. Personally, I'm a fan of milk-shakes... and it would have been great that M&B would be able to deliver but quite frankly over a period of at least six (6!) months I was always told that the machine is broken and that milk-shakes cannot be served. Okay, I can understand that things break and it takes a little bit longer to get them fixed up here on this remote island but... more than six months? A well-known brand like Mugg & Bean? And the shakes are still offered according to their menu cards? Come on! Just put a blank sticker over drinks or meals you cannot serve... But that's not all... choosing other drinks or menus randomly from the menu ended up in a simply answer by the waitress: \"Sorry, it's not available\". I finally gave up, and started to ask what's available today without even bothering to look at the card anymore. And despite the declining trend of drinks and dishes it became obvious that the standards of cleanliness followed the same trend. Guests had to leave the restaurant in order to lighten their pressure around the hips - for several weeks or even months because the rest room upstairs was in a bad state! Sitting near the big window panels with a view on the fountain outside revealed a disgusting observation of grease on the door panels in the cake and tart section. Cups, plates and cutlery usually had a stain from previous meals, too. And finally, one of our MSCC members got some extra nutrition in his pot of tea which submerged in his cup after pouring the final drops of green tea... boiled cockroach! Not surprisingly, any meetups of both user groups were done at other locations in Bagatelle. And there are plenty of other blog articles and photos documenting on that one, too. New management - maybe a fresh wind of service? During the MSCC meetup on entrepreneurship back in August 2014 one of the attendees mentioned that he's very close to the current manager of Mugg & Bean and that the new person in charge would like to meet and listen to our previous experience. Alright, we agreed on a time and location somewhere else in Bagatelle and had a great conversation about the stories you just read in the previous paragraphs. Funny side-note: The M&B manager had a fidelity card from that other cafe. Which might tell you a lot actually... Honestly, I didn't bother to follow up on any kind of improvements but only by listening to other MSCC craftsmen about their recent visits, gave me enough information that it wouldn't be worth to spend any further time at that place. Not now, not in the future. Granting a wish... ... I guess that might be the only explanation why I finally agreed this morning to give it another shot. My BWE was already \"chasing\" me since weeks that she'd love to have a proper breakfast in the morning compared to the range of small bites at Vida e Caffe, also at Bagatelle. Since months, the weekly Code & Coffee meetings of the MSCC have been carried out at the little cafe opposite the Emtel showroom, and so far it had been always great. Even though the music might be too enthusiastic sometimes but the staff is always friendly and pays attention to their clients' demands. Anyway, judgement day at Mugg & Bean... all those months I could convince my BWE that it wouldn't be an option to have a meal there anymore. I can't say what was the key aspect during the drive to Bagatelle but I accepted that we should give M&B another shot. Maybe things might have improved since last time. Okay, okay... let's give it a try. Oh boy! What a fatal mistake! Despite having six people from the waitron staff lurking around near the main entrance or the kitchen passage we didn't get that much attention. The kitchen was not yet ready... I mean, yeah, why would a breakfast place need to have a ready kitchen in the morning? Okay, let's wait for some 15 to 25 minutes then... And let's see about the drinks? My BWE asked for a cup of regular black coffee... \"Sorry, we don't have coffee right now\". What? You have coffee-themed wallpapers all over the place and you cannot serve a simple cup of plain black coffee? On the other hand, my first choice of drink from the menu... \"Sorry, it's not available\". What the flipping banana? Am I cursed in terms of food selections? And no it wasn't a milk-shake! That would have been a good choice as they somehow managed to fix their shake machine. Luckily, ordering our morning dishes wasn't an obstacle at all, just waiting... and waiting... I didn't run a stop-watch but roughly after 45 to 50 minutes later we got our food, well partly. The South African Farm breakfast with additional Beef Strips came first, not my choice and some minutes later I was finally served with a portion of Mexican Scrambles and an additional portion of Country Fries. The beef was not seasoned and tasted blunt after all, the fries had tepid temperature rather than being hot, and taking into consideration that the scrambled eggs might have been put into an oven or microwave judging on the amount of liquid in the small gratin bowl. Honestly, disgusting look and even more horrible taste! Mugg & Bean world-wide - No, thank you! Seriously, it is not my intention to blackmail a brand with this posting but frankly speaking after all those months of continuous negative experience at Mugg & Bean in Bagatelle, I won't have another meal for a very long time in one of their branches. No matter where it is located. And believe me, Mugg & Bean isn't the first franchise I completely ditched for more than a decade. McDonalds' back in Germany had (and still have) that bad habit of putting too much salt on their french fries. And so, I went to spent my money somewhere else. Please share your experience with either Mugg & Bean (Bagatelle or somewhere else) or other food places. Thanks! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mb-nightmare-at-bagatelle.html'; this.page.identifier = '9271ed1c97_idmb-nightmare-at-bagatelle'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mcsa-linux-on-azure.html": {
    "href": "mcsa-linux-on-azure.html",
    "title": "Next stop: MCSA: Linux on Azure - Get Blogged by JoKi",
    "summary": "Next stop: MCSA: Linux on Azure Copy Markdown View Markdown GitHub Markdown Source Next stop: MCSA: Linux on Azure Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mcsa-linux-on-azure'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mcsa-linux-on-azure'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Next stop: MCSA: Linux on Azure') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-03-13 - Filed under LinuxPersonal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Recently I decided to coordinate my work and learning activities a little bit. Turns out that while working with .NET Core, in particular developing an API project, I do quite some coding under Linux using Visual Studio Code. During office hours I'm fully emerged into Visual Studio 2017 running on my Windows 10 machine but often I'm reviewing and tweaking some of my code during the evening hours on my secondary Xubuntu system. Linux and me The story about me using Linux goes back two decades. Actually, if I remember correctly it happened some time in summer of 1996 when I officially purchased a copy of S.u.S.E. Linux 4.2. At that time I was still studying Applied Chemistry at the University of Kaiserslautern and the Unix AG on the campus offered copies of Slackware among others for free; you only had to bring the empty CDs to get the software burned on. Note: The Unix AG was founded and still is run by a group of students and assistants in the field of computer science. And at that time there was a nice fellow named Klaus Knopper, famously known for his Linux distribution Knoppix. Apart from attending lectures and running experiments in the chemistry laboratory I spent a good amount of time in the university's computer labs, too. Over there you had access to graphical XTerminals running on AIX Unix compared to the regular ASCII terminals anywhere else on campus. The two reasons I bought a copy of S.u.S.E were because I wanted to set up an internet gateway at home which I was not able to do so with Windows NT 4.0, and because the distribution was bundled with several books on installation, network configuration and Linux in general in German language. So, I started the initial installation on a Friday afternoon, worked through the whole night reading and configuring the system several times, and slept only a few hours over the whole weekend. Finally, on Monday morning after several attempts and lots of swearing/ranting over my own incapabilities I managed to run a working internet gateway. Dialup happened over ISDN on my freshly installed Linux computer while my parent's system running Windows 95 was attached to the 10base2 thin Ethernet network. The rest is history... Azure is running (on) Linux Eventually you might be aware of the situation that Microsoft is actually using Linux technology to run its cloud solution named Azure. Yes, they do... According to an article Whoa. Microsoft is using Linux to run its cloud published on Wired back in September 2015 it is referring to an official blog article by Microsoft. Get more details about the Azure Cloud Switch in Microsoft showcases the Azure Cloud Switch (ACS) by Kamala Subramaniam Principal Architect, Azure Networking. It [note: The Azure Cloud Switch (ACS)] is a cross-platform modular operating system for data center networking built on Linux. Nonetheless, I would assume that the main interest would be to run and operate Linux machines in Azure. According to Microsoft says 40 percent of all VMs in Azure now are running Linux we are in good company with like-minded system operators. What better than combining two technology stacks? Although, I work on Windows systems during my day job, Linux plays a vital role. Our internet gateways are based on a designated Linux system which handles all internal traffic and provides access to the internet by providing essential services like DHCP, DNS, proxy and so forth. Services the standard router provided by a local ISP might not be capable of or with serious security concerns. Using Azure to provision a Linux-based virtual machine takes less than 5 minutes and there are various options available. I'm a big fan of Xubuntu but to prepare myself for MCSA: Linux on Azure I'm going to need a CentOS based system. So, instead of taking resources on my local machine using a virtualisation software like VirtualBox or VMware I'm going to entertain a Linux VM on Azure. It's more convenient after all. MCSA: Linux on Azure Combining both technology stacks into one sounds almost like a dream coming true for me. Using Linux has always been a passion and fun factor for me and being able to add it more and more to my professional services brought me to the decision to look into the benefits and requirements of Microsoft's MCSA: Linux on Azure certification. Effectively, the exam requirements stipulate that one has to pass two independent certifications to achieve MCSA: Linux on Azure: Linux Foundation Certified System Administrator (LFCS) 70-533 Implementing Microsoft Azure Infrastructure Solutions You might have noticed that it is not purely a Microsoft certification but integrates the work of the Linux Foundation. Interestingly Microsoft officially announced during the Connect(); 2016 that they joined the Linux Foundation as a Platinum Member. Which literally made the Linux on Azure certification possible. Our membership to the Linux Foundation builds on our work with the foundation, including the creation of a Linux on Azure certification. Exciting times, don't you think? Exam formats Both, Microsoft and the Linux Foundation, offer details about the skill sets being measured during the exams. The Microsoft exam 70-533 is based on the usual multiple choice format. Compared to that the LFCS is performance-based. Candidates will need to perform tasks or solve problems using the command line interface in their chosen Linux distribution. Meaning, you connect to an actual Linux system - running either CentOS 7 or Ubuntu 16 (as of writing) - and you have to get your \"hands dirty\" in order to qualify. Learning resources Check out the section Optional training and resources on the official LFCS website. The Linux Foundation provide free material like their Certification Candidate Handbook, their Certification Preparation Guide, and their LFSx01 courses online. In similar fashion Microsoft lists multiple resources in the Preparation options of the exam 70-533. The online training is accessible for free through the edX platform and are part of the Microsoft Professional Program in Cloud Admin, too. Using the same preparation material gives you the ability to achieve a second accreditation. Perhaps you are interested to read more about the Cloud Administration professional program. Having an active, annual subscription with Pluralsight I browsed through their learning paths and discovered Pluralsight Path to MSCA: Linux on Azure. It's a combination of several courses provided by experts John Savill and Andrew Mallett. More resources will be added regularly to my 100-days-of-exam repository on GitHub. You are hereby invited to fork it, to add more resources including other exam preparations, and to send me your pull requests (PRs). Commitment to #100DaysOfExam To keep myself accountable I am committed to the #100DaysOfExam challenge. I will learn and prepare for an exam for at least an hour every day for the next 100 days. Following the Rules section of #100DaysOfExam I will tweet about my progress using hashtag #100DaysOfExam and I will update my Log with the day's progress and provide a link every day, too. Let's do it! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mcsa-linux-on-azure.html'; this.page.identifier = '9271ed1c97_idmcsa-linux-on-azure'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "meine-tropischen-inseln.html": {
    "href": "meine-tropischen-inseln.html",
    "title": "Meine tropischen Inseln - Get Blogged by JoKi",
    "summary": "Meine tropischen Inseln Copy Markdown View Markdown GitHub Markdown Source Meine tropischen Inseln Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'meine-tropischen-inseln'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'meine-tropischen-inseln'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Meine tropischen Inseln') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-14 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Südsee-Flair mit Strand, Palmen und Cocktails mitten in Brandenburg? Klar, kein Problem mit der Schwimmoase My Tropical Islands. Nach diesem ausgiebigen Tag der Fuchsprogrammierung steht der heutige Abend ganz im Zeichen der Entspannung. Und wenn man schon mal im Teltow unterwegs ist, was böte sich dann mehr an, als mal einen Besuch in Tropical Islands zu zu tätigen. Das Freizeitbad ist 24h rund um die Uhr geöffnet und selbst Übernachtung mit Frühstück ist buchbar. Noch kurz ins Hotel, Badehose und Handtücher geschnappt und ab in den Süden... wortwörtlich \uD83E\uDD2A Der ehemalige Flughangar für Zeppeline hat schon eindrucksvolle Ausmaße... und mit der Beleuchtung von innen heraus, sieht das Teil ziemlich cool aus. Die Nacht ist mit 20,5°C ziemlich lau und in der Halle wird's gleich noch ein paar Grade angenehmer. Und ab geht's in die Bali Lagune und ein wenig Schultermassage unterm Wasserfall, danach in den Strömungskanal... Herrlich, genau das richtige nach einem Tag wie diesem. Leider konnten wir nicht zu lange bleiben, da morgen ja noch ein weiterer Tag Workshop angesetzt ist, und so machen wir uns kurz vor Mitternacht wieder auf den Rückweg zum Hotel. Ich habe mir das Tropical Islands auf alle Fälle für einen erneuten Besuch vorgemerkt, und dann evtl. sogar länger als nur ein paar Stunden. Mal sehen, potentiell sogar mit Übernachtung und allem drum und dran. Insbesondere würden mich die Shows und Attraktionen tagsüber interessieren. Mal sehen... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'meine-tropischen-inseln.html'; this.page.identifier = '9271ed1c97_idmeine-tropischen-inseln'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mentor-mitgsl.html": {
    "href": "mentor-mitgsl.html",
    "title": "Tech Mentor at MIT Global Startup Lab in Mauritius - Get Blogged by JoKi",
    "summary": "Tech Mentor at MIT Global Startup Lab in Mauritius Copy Markdown View Markdown GitHub Markdown Source Tech Mentor at MIT Global Startup Lab in Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mentor-mitgsl'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mentor-mitgsl'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Tech Mentor at MIT Global Startup Lab in Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-08-16 - Filed under CommunityPersonal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Being part of the first Global Startup Lab by the Massachusetts Institute of Technology (MIT) in Mauritius as a technological mentor for the participating project teams didn't come overnight. Let's start from the beginning... I'm not sure about the exact date but if I remember correctly it was back in July 2013 when Dhaval Adjodah got in touch with me talking about a technology business incubator program in Mauritius. Then mid of August Dhaval came to one of the weekly Code & Coffee sessions by the Mauritius Software Craftsmanship Community (MSCC). He was genuinely interested to know more about the local IT scene, and the activities of our community. At the time, I explained to him that the MSCC somehow represents my passion to share information with other IT people, and that I'm looking forward to develop a vital and thriving community with other enthusiastic geeks like me. We talked about the knowledge gap of fresh graduates between academia and the real demands at work. He asked me about my opinion on the state of entrepreneurship in Mauritius in general. He on the other hand shared his plans about running this kind of technology-oriented workshop in Mauritius. As the conversation went on, we covered one or two other topics (which I can't remember anymore)... Anyway, the perspective of having an incubator program run by the MIT in Mauritius would be outstanding. Four years later... Yes, four years! Back in January 2017 I contacted Dhaval to inform him about the annual Developers Conference, and I was interested to know whether his idea around the incubator program started to take form. He informed me that they are planning to run it by early June until mid of August. It was just about forming a team of instructors that would be willing to come to Mauritius for that period. Next time, we exchanged updates back in March, just a couple of days before the conference, and Dhaval informed me that everything has been settled. A team of MIT instructors would be ready to come over. The only thing missing are applicants and their technology ideas for the incubator program. Finally, in April the official announcement came that the MIT Global Startup Lab in corporation with Mauritius Telecom is looking for applicants to join the program. The information was shared to all MSCC members immediately. Kick-off... Meanwhile, Dhaval sent me an official invitation to the inauguration ceremony and gave me details about the final project demonstration day. Of course, I accepted the call. During the opening of the incubator program I was pleasantly surprised (and felt kind of proud) to see a solid number of software craftsmen among the participants. Really good to see that the MSCC is part of such an interesting and important activity here on the island. It was great to exchange with those soon-to-be entrepreneurs and to listen to their business ideas and technology solutions to solve real problems. The diversity of concepts was immensive and very broad. Some ideas seemed to be specialised, others too obvious. And last but not least, Dhaval introduced me to the team of MIT instructors who would get all the project teams with their idea into shape, and to guide and train them to pitch their minimum viable product (MVP) by the end of the incubator program. Great folks! Dragon's Den Some weeks in, I received an email by Zehreen to come and see the teams' concepts. Eventually, this should be understood as some kind of test-run as to convince some potential investors. Boy, this reminded me of that infamous TV show and yeah, that would be fun to listen to the various pitches, and then dissect their concepts, and roast their business plans. I'm in... Even though the incubator program was only half-way through I have to admit that the quality of presentations, the team spirit and the actual business ideas were good. There was a bit of criticism in regards to sustainable business plan, and the projected return of investments (ROI). Which seems to be okay given that all participants are not business school graduates. The actual mentorship Following the audit of projects I was kindly requested whether I would be available as a technological advisor for one or more teams. Mentorship is a relationship in which a more experienced or more knowledgeable person helps to guide a less experienced or less knowledgeable person. Given the years of experience, and having set up several companies during that period I agreed and was assigned to a team which is going to disrupt the education market in Mauritius, more precisely their idea is focused on students' management at primary and secondary schools. The team Schoolify (formerly known as eduCare) already had their technology and development stack defined, and I confirmed their decisions as reliable and scalable for upcoming demands. Furthermore, I gave the team a list of online resources to review and to learn from in order to increase the quality of their software development. We agreed to meet at least once per week until demo day in some weeks time. Project Demo Day The climax of the MIT Global Startup Lab is the presentation of each teams' final project, well at that stage it might be better to talk about an actual product. Of course, over the course of the program the number of participants decreased but there were still 10 teams left for Demo Day. Outstanding perseverance... And \"Thumbs Up!\" already to all teams. Following are snapshots of each team (in no particular order) Exponent AI - Search engine for lawyers AVR-Tech - AR Study-aid for medical students BioSphere - Intelligent Energy Management System motrackr - Virtual coach assistant mo Aprann - Peer to Peer tutoring service FoodBAE - Online Food Ordering AVR-Tech - Med-AR, AR Study-aid for medical students CodaxFi - Learn it, code it, live it: Connecting platform empowering women and girls in coding TechGardeners Bubble Garden - Your garden at your fingertips Schoolify - School Management System Demo Day was done in a similar way as the \"Dragon's Den\" test-run earlier. Major difference was the official aspect, the presence of the press, media and a broader audience from the public, and last but not least potential investors to pick up and support one or two teams. Mauritius Telecom generously donated prize money for four different categories. Social Impact Award Innovative Technology Award Sustainable Business Model Award Grand Prize Proudly I can say, that three of the winning teams have MSCC craftsmen among their team members. How cool is that? MIT Global Startup Lab in Mauritius: A Success All finalists had their MVPs set up and ready for demonstration. Even though it was a long day already many people from the audience went to see their products in use. See also related articles in the media: Formater le futur avec le Mauritius Telecom Campus - ict.io MIT Global Start-Up Labs: l’équipe d’Ish Sookun l’emporte haut la main - L'Express.mu Le MIT GSL accompagne les jeunes entrepreneurs à Maurice - ict.io MIT GSL Demo day : découvrez les quatre lauréats - ict.io Final thoughts Thanks to all the participants. You did an amazing job and I hope that you all understand this incubator program as a serious start to a new career. You all had great ideas, transformed them into actual concepts, and created an MVP in 8 weeks only. That is quite an achievement already. Now, continue your journey and show every Mauritian that disruptive ideas grow among us. All the best and a prosperous business in the future. Special thanks go out to the team of responsible people from the MIT. Namely, Dhaval, Bayo, Zhizhuo, Dishita, Awa, and Zehreen - thanks for bringing the Global Startup Labs to Mauritius, and thanks for your engagement to bring those ideas to life. Thank you for letting me be part of it. According to the general feedback I received, probably this might not be the last MIT Global Startup Lab in Mauritius but just the beginning. I'm looking forward to the next edition. Cheers, JoKi PS: Bayo, thanks for your contribution to the Windows Phone, mainly dual SIM feature and others. \uD83D\uDE09 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mentor-mitgsl.html'; this.page.identifier = '9271ed1c97_idmentor-mitgsl'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "michael-moore-bzw--die-qual-der-wahl.html": {
    "href": "michael-moore-bzw--die-qual-der-wahl.html",
    "title": "Michael Moore bzw. die Qual der Wahl - Get Blogged by JoKi",
    "summary": "Michael Moore bzw. die Qual der Wahl Copy Markdown View Markdown GitHub Markdown Source Michael Moore bzw. die Qual der Wahl Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'michael-moore-bzw--die-qual-der-wahl'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'michael-moore-bzw--die-qual-der-wahl'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Michael Moore bzw. die Qual der Wahl') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-19 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Also, ich find' den Mann absolut amüsant und herzerfrischend. Ich weiss ja nicht wie es euch geht, aber nachdem ich gerade am Schmökern der Bücher von ihm Querschüsse Stupid White Men Aufpasst, Mr. Bush bin, muss ich sagen, dass es doch einige Informationen über die Verhältnisse der amerikanischen Wirtschaft und Politik in einem anderen Licht erscheinen lässt. Und in gewisser Weise untermauern seine Fragen auch meine eigenen Fragestellungen. Ich möchte damit garantiert nicht sagen, dass ich kontra Amerika bin, nein, ganz sicher nicht. Ich halte die USA für einen wunderbaren Staat. Besonders mit der herrlichen Fauna und Flora. Moores Aussagen geben zumindest mir auch Anreize die Politik und Wirtschaft im eigenen Staate gewissenhafter im Aug ezu behalten. Denn bei Menschen mit Macht ist stets mit irgendwelchen Überraschungen zu rechnen - im guten wie im negativen Sinne. Oder sind wir alle zu naiv? Auch die laufenden Anti-Moore-Kampagnen haben meinen Geist genährt. Ehrlich gesagt, finde ich es klasse, dass etwas passiert. Denn das ist meiner Meinung nach die wichtigste Eigenschaft der Bevölkerung in solchen Momenten. Ich konnte es bei den Wahlen 1998 und 2002 nicht ganz nachvollziehen, dass das Wahlrecht von etlichen mündigen Staatsbürgern weggeworfen wurde. Ich hatte in diesem Zusammenhang auch 'ne nette Unterhaltung mit meinen jüngeren Geschwistern: Wie soll ich wählen gehen? Well, die erste Antwort ist natürlich: Weil es deine Pflicht als Bürger ist. Die weiteren Antworten liefen dann verstärkt auf mathematischer Basis (Prozentrechnung und so... ), aber letztendlich sind wir dann alle gemeinsam auf den Punkt gekommen, dass man immer wählen muss. Auch wenn man überhaupt keinen Bock hat oder wenn es egal scheint, was man wählt... Leute, dann bleibt immer noch eine wesentlich bessere Alternative als Nicht-Wahl übrig: Ungültige Stimmabgabe! Aber nunja, wahrscheinlich übersteigt diese Antwort den Horizont etlicher Bürger, zumindest wenn man der PISA-Studie zum Stand der allgemeinen Bildung ein wenig Glauben schenken darf. Ich freu' mich schon auf Neu-Wahlen... \uD83E\uDD2A Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'michael-moore-bzw--die-qual-der-wahl.html'; this.page.identifier = '9271ed1c97_idmichael-moore-bzw--die-qual-der-wahl'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "microsoft-and-innovation-iif-method.html": {
    "href": "microsoft-and-innovation-iif-method.html",
    "title": "Microsoft and innovation: IIF() method - Get Blogged by JoKi",
    "summary": "Microsoft and innovation: IIF() method Copy Markdown View Markdown GitHub Markdown Source Microsoft and innovation: IIF() method Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-and-innovation-iif-method'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-and-innovation-iif-method'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Microsoft and innovation: IIF() method') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-10-06 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); This Saturday I was watching a couple of eLearning videos from TrainSignal (thanks to the subscription I have with Pluralsight) on Querying Microsoft SQL Server 2012 (exam 70-461). 'Innovation' by Microsoft I kept myself busy learning 'new' things about Microsoft SQL Server 2012 and some best practices. It was incredible 'innovative' to see that there is an additional logic function called IIF() available now: Returns one of two values depending on the value of a logical expression. IIF(lExpression, eExpression1, eExpression2) Ups, my bad... That's actually taken from the syntax page of Visual FoxPro 9.0 SP 2. And tada, at least seven (7+) years later, there's the recent IIF() Transact-SQL version of that function: Returns one of two values, depending on whether the Boolean expression evaluates to true or false in SQL Server 2012. IIF ( boolean_expression, true_value, false_value ) Now, that's what I call innovation! But we all know what happened to Visual FoxPro... It has been reincarnated in form of Visual Studio LightSwitch (and SQL Server). Enough ranting... Happy coding! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'microsoft-and-innovation-iif-method.html'; this.page.identifier = '9271ed1c97_idmicrosoft-and-innovation-iif-method'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "microsoft-commities-ganz-schn-schlau.html": {
    "href": "microsoft-commities-ganz-schn-schlau.html",
    "title": "Microsoft Communities - Ganz. Schön. Schlau. - Get Blogged by JoKi",
    "summary": "Microsoft Communities - Ganz. Schön. Schlau. Copy Markdown View Markdown GitHub Markdown Source Microsoft Communities - Ganz. Schön. Schlau. Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-commities-ganz-schn-schlau'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-commities-ganz-schn-schlau'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Microsoft Communities - Ganz. Schön. Schlau.') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-12-01 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Communities sind derzeit auch in der Windows-Welt verstärkt am Kommen. Daher ist es auch kaum ein Wunder, dass ich in den nächsten Tagen und Wochen auf etlichen Usertreffen zu finden sein werde. Neben der monatlichen Organisation des VFP Stammtisches in Speyer gesellen sich weitere hinzu... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'microsoft-commities-ganz-schn-schlau.html'; this.page.identifier = '9271ed1c97_idmicrosoft-commities-ganz-schn-schlau'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "microsoft-empower-fr-isvs.html": {
    "href": "microsoft-empower-fr-isvs.html",
    "title": "Microsoft Empower für ISVs - Get Blogged by JoKi",
    "summary": "Microsoft Empower für ISVs Copy Markdown View Markdown GitHub Markdown Source Microsoft Empower für ISVs Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-empower-fr-isvs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-empower-fr-isvs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Microsoft Empower für ISVs') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-20 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Heute gab's wieder eine tolle Nachricht von unserer CLIP-Betreuerin Dorothea. Bannerwerbung für das Microsoft Empower für ISVs Programm. Coole Sache... Allein die Einleitung zu Empower liest sich doch bereits wie ein El Dorado für Entwickler, oder? Unsere Empower-Initiative richtet sich an Entwickler und Softwarehersteller (Independent Software Vendors, ISVs), die schnell und effizient Software auf der Basis neuester Microsoft-Technologie entwickeln und intensiv mit uns zusammenarbeiten wollen. Microsoft Empower für ISVs läuft ein Jahr; danach haben Sie die Möglichkeit, Ihre Mitgliedschaft um ein weiteres Jahr zu verlängern. Die Teilnahme an unserer Empower-Initiative kostet für zwölf Monate 320 Euro zzgl. MwSt. Für gerade mal 320 Euro / 12 Monate, also noch nicht mal 27 Euro / Monat gibt's die volle Packung aus dem Hause Microsoft. Entwicklerherz, was willst du mehr? Ich für meinen Part würde, sofern ich nicht bereits die Lizenzen hätte \uD83E\uDD2A direkt zuschlagen und loslegen. Die Voraussetzungen und Zielsetzung für die Nutzungbedingungen sind absolut locker. Und binnen einen Jahres hat jede/r fähige Entwickler/in garantiert ein cooles Projekt aus dem Boden gestampft. Hm, falls ich mal Lizenzen brauchen sollte... Werde ich auf alle Fälle die Vorzüge von Empower für ISVs in Anspruch nehmen. Derzeit habe ich keinen Bedarf, aber locker zwei Projekte am Start, die ad hoc die Bedingungen für das Programm erfüllen würden. Über Projekt: Sunshine als Protokollsoftware für sportliche Aktivitäten habe ich ja bereits ein wenig geschrieben. Und das zweite wird noch nicht an die große Glocke gehängt, aber es ist in der Planungs- und Designphase. Mehr dazu frühestens 2007. Sunshine hat aktuell die höhere Priorität. Also, ihr vielen Entwickler da draußen in den Weiten des Internets... Nicht lange überlegen, sondern machen. Am besten noch mit dem Zusatz, dass ihr eure Lösungen in Visual FoxPro realisieren werdet. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'microsoft-empower-fr-isvs.html'; this.page.identifier = '9271ed1c97_idmicrosoft-empower-fr-isvs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "microsoft-sharepoint-2013-administration-inside-out.html": {
    "href": "microsoft-sharepoint-2013-administration-inside-out.html",
    "title": "Microsoft SharePoint 2013 Administration Inside Out - Get Blogged by JoKi",
    "summary": "Microsoft SharePoint 2013 Administration Inside Out Copy Markdown View Markdown GitHub Markdown Source Microsoft SharePoint 2013 Administration Inside Out Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-sharepoint-2013-administration-inside-out'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-sharepoint-2013-administration-inside-out'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Microsoft SharePoint 2013 Administration Inside Out') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-11-13 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Microsoft SharePoint is a great product to work with - no doubt, no questions asked but it can be (and is) quite confusing at any stage of your knowledge. Luckily, I had access to this title when I needed it most... For a software development job I had to set up a Microsoft SharePoint 2013 test lab, and honestly without this book it wouldn't have been possible. Check out the original (and first) review as published on Amazon: Great reference - a must-read! Microsoft SharePoint is a great product to work with - no doubt, no questions asked but it can be (and is) quite confusing at any stage of your knowledge. And basically you cannot use SharePoint to the maximum. I was already following some online video courses with Brian Alderman prior to reading this title hence my expectations were already at a higher stake than usual. The other authors didn't ring a bell in me but I was pretty sure that they would easily match the high quality of content. The book is actually more like a compendium. The overall structures of chapters is well organised and all chapters are written in a very detailed style. There are a lot of PowerShell scripts as most configuration of SharePoint is done via scripting. Sometimes, this is actually a little bit messy to read and to stay focused on the actual task. But the others took care of that and there are online resources to actually get all scripts to do the job. Speaking about authors, given the circumstances that we have a pool of authors it happens that certain information is repeated and in rare occasions there are controversial statements for the same functionality. Kind of confusing and my reasoning to downgrade my rating a bit. Apart from that, this title is very easy to understand, filled with lots of PowerShell scripting code, and practical advice and guidance to achieve proper planning, installation, configuration and operation of Microsoft SharePoint 2013. Independent whether you need to have an test environment for software development, or you have to set up a full-fledged server farm with a good number of servers. As stated initially, I started to read this book already last year during my 30 Days of SharePoint challenge prior to implement a SharePoint client using the Client Side Object Model (CSOM) interface. This book on installation, configuration and administration of Microsoft SharePoint 2013 was a heck of an asset and even though I initially had to deal with an installation of SharePoint 2010 at customer's site, I was in pretty good position to move on to a fully virtualised test bed on Microsoft Azure. Overall it took approximately four to five hours from creating the private network (VPN) on Azure, allocating the various Windows servers for the \"perfect\" development infrastructure and to get Visual Studio 2013 hooked up for some serious software development. I'd assume that it might have taken me days without the lecture of this title. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'microsoft-sharepoint-2013-administration-inside-out.html'; this.page.identifier = '9271ed1c97_idmicrosoft-sharepoint-2013-administration-inside-out'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "microsoft-virtual-academy.html": {
    "href": "microsoft-virtual-academy.html",
    "title": "Microsoft Virtual Academy - Get Blogged by JoKi",
    "summary": "Microsoft Virtual Academy Copy Markdown View Markdown GitHub Markdown Source Microsoft Virtual Academy Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-virtual-academy'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'microsoft-virtual-academy'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Microsoft Virtual Academy') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2011-10-13 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Carpe Diem It's been since a while that I could write an article for this blog but alas, I was (and still am) very busy with customer's work. Which is actually good. So, what is this article going to tell you? Well, in general, just what I already tweeted, that life is constant process of learning - especially as software craftsman. Due to an upcoming new customer project in ASP.NET I had to seize the opportunity to get my head deeper into latest available technologies, like Windows Azure and SQL Azure. I know... cloud computing and so on is not a recent development and already available since quite a while but I never any means to get myself into this since roughly two weeks ago. Microsoft Virtual Academy I can't remember exactly what guided me towards the Microsoft Virtual Academy (MVA), oh wait... Yes, it was a posting on Facebook from an old Community Leader/Insider Program (CLIP) community friend. He posted a shortened URL with #MVA tag that caught my attention. Thanks for that Thomas Kuberek. After the usual sign in or registration via Live ID I was a little bit surprised that Mauritius is not an available country option... Quick mail exchange with the MVA Decan, and yeah, apologies for the missing entry. So, currently I'm learning about Microsoft products and services, and collecting points under \"Not Listed Country\" until Mauritius is going to be added. Hopefully soon, as MVA honors your effort with different knowledge ranks that are compared to other students with public profiles. I think it's a nice move to add some game and competition factor into the learning game. The tracks and their different modules are mainly references to publicly available material online, namely on either MSDN, TechNet, Channel9, or other Microsoft based sites. The course material therefore also varies in different media and formats, ranging from simple online articles over downloadable documents (.docx or .pdf) to Silverlight / Windows Media streams with download options. Self-assessment and students ranking Each module in a track can be finished by taking part in a self-assessment. Up to now, the assessment I did (and passed) were limited to 10 minutes available time, and consisted of six to seven questions on the module training material. Nothing too serious but it gives you a glimpse idea how Microsoft certification exams are structured. Conclusion Nothing really new but nicely gathered, assembled and presented to the MVA students. At the moment, I wouldn't dare to compare the richness and quality of those courses with professional training offers, like Pluralsight .NET Training, LearnDevNow, VTC, etc. at all, but I think that MVA has potential. Give it a try, and let me know about your opinions. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'microsoft-virtual-academy.html'; this.page.identifier = '9271ed1c97_idmicrosoft-virtual-academy'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "migration-joomla-ghost.html": {
    "href": "migration-joomla-ghost.html",
    "title": "Migration to Ghost - Get Blogged by JoKi",
    "summary": "Migration to Ghost Copy Markdown View Markdown GitHub Markdown Source Migration to Ghost Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'migration-joomla-ghost'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'migration-joomla-ghost'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Migration to Ghost') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-08-07 - Filed under DevelopmentGeneral (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); With the announcement of Ghost 1.0 I gained some motivation to take care of my blog. It's been untouched since a very long time and honestly I didn't feel like writing my articles in plain HTML anymore. Instead, using Markdown feels more \"natural\" than HTML. Surprisingly, I also discovered that Ghost 1.0 is now using MySQL or MariaDB as data store. During the camp-site fire conversation with John O'Nolan back in February 2017 the emphasis was clearly on SQLite as the fastest option. Seems that enterprise requirements in regards to backup and load balancing might have given the decision a different turn. Which to my opinion is positive. No worries, SQLite is still around for local development. Old blog: Joomla 1.x Yes, yes... I know. It's 2017 and how can someone still operate a website running on Joomla 1.x. As mentioned earlier, lack of motivation to maintain the blog after all. Also, I modified the code base in my blog beyond the template, and there had been some additional gems, like ID-free URLs and others which newer versions of Joomla wouldn't provide (at least at times I checked). Modernisation With more and more constraints being introduced by Google in regards to more performance, mobile-first approach and increased demand on security of websites, I like the features of Ghost that already take care of this. Namely, fully responsive template, Google AMP, new importer since version 1.0, new editor, and so forth. Read their announcement for further details. Even though I was using a desktop & mobile client application to manage content in my Joomla-based blog, I really appreciate the Ghost Desktop client which itself is an Electron-based version of the Ghost Admin website. It was high-time for an overhauling of my personal blog. Since the conversation with John back in February I already made up my mind to use Ghost as the new platform, and with the availibility of Ghost 1.0 it was a done deal. Migration path Following is a brief overview of my migration steps from Joomla 1.x to Ghost 1.x: Backup existing Joomla database and blog content, like images, etc. Restore Joomla database into latest version of MariaDB on a local development machine. Write SQL query to map Joomla content and category columns to Ghost JSON properties using MySQL Workbench. Export query results using Workbench's \"Export to File\" feature. Clean up exported JSON files with various RegEx-based search and replace expressions. Export sample JSON from local development instance of Ghost. Replace exported Ghost content with cleaned up Joomla content. Import merged JSON back into local development instance of Ghost. Verify migrated content and run some final adjustments in Ghost database directly. Apply necessary changes to Ghost template. Export local Ghost content and template and import it into production instance on web server. Handle URL rewrites in web server. Eventually, over time and monitoring there might be additional adjustments necessary on the production instance of Ghost but I'm not too concerned about that after all. Backup of existing Joomla blog First, create a backup of the MySQL database like so: $ mysqldump -u root -p --databases joomla > mysql_joomla.sql Next, securily transfer the SQL backup and all assets form your Joomla installation to your local machine. You can either use your favourite FTP client software, ie. FileZilla, or simply scp the data like so: $ scp -r user@server:/var/www/joomla/* ./ Of course, you have to use your path information. Restore Joomla 1.x database in latest MariaDB If you need more information about how to restore a MySQL/MariaDB database please refer to the official documentation. I ran the following statement: mysql -u root -p < mysql_joomla.sql SQL query to map Joomla columns to Ghost properties Partly thanks to my natural laziness I gave Google a shot and among the search results I landed on Migrating from Joomla to Ghost by Mike Seddon which gave me a headstart into my adventure. Mike wrote his tutorial almost 2 years ago, and obviously it was for an earlier data model of a Ghost post. So, with minor changes I managed to produce the following SQL queries to extract my blog posts, categories and the relationships between them: -- MigrateJoomlaGhost_Posts.sql SELECT id as 'id' ,null as 'uuid' ,`title` as 'title' ,`alias` as 'slug' ,null as mobiledoc ,Coalesce(`introtext`,`fulltext`, '') as 'html' ,null as 'amp' ,REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REPLACE( REGEXP_REPLACE( REGEXP_REPLACE(Coalesce(`introtext`,`fulltext`, '') ,'(<img.*?src=\"(?:[^\"]*?([^/\\/\\]+(?:jpg|gif|png|JPG)))\".*?alt=\"([^\"]*)\".*\\/>)' ,CONCAT('![\\\\3](/content/images/',DATE_FORMAT(`created`,'%Y/%m/'),'\\\\2)')) ,'(<p>(.*)<\\/p>)' ,CONCAT('\\\\2','\\r\\n')) ,' ',' ') ,'JPG','jpg') ,'<strong>','**') ,'</strong>','**') ,'<h4>','####') ,'</h4>','') ,'<em>','*') ,'</em>','*') as 'plaintext' ,null as 'feature_image' ,0 as 'featured' ,0 as 'page' ,'published' as 'status' ,'en' as 'locale' ,'public' as 'visibility' ,null as 'meta_title' ,metadesc as 'meta_description' ,1 as 'author_id' ,UNIX_TIMESTAMP(`created`)*1000 as 'created_at' ,1 as 'created_by' ,Coalesce(UNIX_TIMESTAMP(`modified`), UNIX_TIMESTAMP(`created`)) * 1000 as 'updated_at' ,1 as 'updated_by' ,Coalesce(UNIX_TIMESTAMP(`publish_up`), UNIX_TIMESTAMP(`created`)) * 1000 as 'published_at' ,1 as 'published_by' FROM joomla_content WHERE state=1 I'm only interested in published articles as indicated by the WHERE clause of the query. Provided to have the full backup of my old blog I'd be able to transfer more content at a later stage. Just in case... Any Joomla categories could be mapped to Ghost tags using the following SQL query: -- MigrateJoomlaGhost_Categories.sql SELECT id as 'id' ,title as 'name' ,alias as 'slug' ,REPLACE(REPLACE(description,'<p>',''),'</p>','') as 'description' ,null as 'feature_image' ,parent_id as 'parent_id' ,'public' as 'visibility' ,null as 'meta_title' ,null as 'meta_description' ,null as 'created_at' ,1 as 'created_by' ,null as 'updated_at' ,1 as 'updated_by' FROM joomla_categories WHERE published = 1 And alias NOT IN ('topbanner'); In my case I'm excluding one category explicitly as it contains just one article which I used for some magic tricks in Joomla before. Nothing to be concerned about. Eventually, you might either adjust the alias selection or remove it completely from the WHERE clause. And last, I took care of the assignments between posts and tags, too. -- MigrateJoomlaGhost_PostsCategories.sql SELECT null as 'id' ,post.id as 'post_id' ,cat.id as 'tag_id' ,0 as 'sort_order' FROM joomla_content post INNER JOIN joomla_categories cat On cat.id = post.catid Where post.state = 1 Please, don't just copy and paste those queries. You have to check the tables names in the FROM and JOIN clause first. Exporting the SQL data to JSON MySQL Workbench has a built-in feature to Export recordset to an external file with multiple format choices. Set the export format to JSON and give your query results a proper file name. After this, I had three JSON files to play with. Using RegEx to search and replace content Even though newer versions of MariaDB have the REGEXP_REPLACE function I went forward to clean up my JSON content in my favourite text editor: Visual Studio Code. Thanks to the legacy of over 10 years I wanted to clean up all content. So, for this task I needed some RegEx magic to convert old fragments of BBcode to Markdown and therefore took some inspiration from the following answer to 'Converting phpBB BBCode posts to Markdown' on Stack Overflow. Out with old, in with the new... \uD83D\uDE09 Additionally, I changed the URL pattern of Joomla internal links to Ghost post slugs and upgraded all hyperlinks to HTTPS protocol. Export Ghost default content as template, replace and import blog content To keep things easy I exported the existing default content from my local Ghost development instance, opened the resulting JSON file in Visual Studio Code and replaced the following arrays with the freshly cleaned up content from my Joomla blog: posts tags posts_tags And as a final touch, I adjusted the users.id to match the publisher ID. Note: Depending on the number of blog articles the import can take quite some time. Invalid JSON won't be imported by Ghost. First verification of migrated data After successful import of all blog articles and tags I skimmed the content in the Ghost Admin and it looked pretty already. Unfortunately, there were still some minor tasks to do to polish the results but this is mainly content specific to my environment, ie. I use a separate sub-domain for static content like images, etc. But overall that was the major part of the migration from Joomla 1.x to Ghost 1.x. Apply changes to Ghost template As a start I was searching the internet for some ready-to-use Ghost template but I have to admit that I wasn't really impressed by the results found. Yes, there are some really talented designers and developers out there on the interweb but taste varies. Finally, I copied the default Ghost template Casper and started with my modifications. Following are the Handlebar files I changed or added to my personalised Ghost template. The snippets are incomplete but provide enough information to find the anchors in the original file. -- default.hbs {{!-- Base Meta --}} <title>{{meta_title}} - {{@blog.title}}</title> <meta name=\"HandheldFriendly\" content=\"True\" /> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" /> {{!-- Styles'n'Scripts --}} <link href=\"{{asset \"built/screen.css\"}}\" rel=\"stylesheet\" type=\"text/css\" /> <link href=\"//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css\" rel=\"stylesheet\" integrity=\"sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN\" crossorigin=\"anonymous\" /> This adds the blog title as a suffix to every page and I like the use of Font Awesome on my site, too. -- partials/post-card.hbs <a class=\"post-card-content-link\" href=\"{{url}}\" aria-label=\"{{#if meta_title}}{{meta_title}}{{else}}{{title}}{{/if}}\"> <header class=\"post-card-header\"> {{#if tags}} <span class=\"post-card-tags\">{{tags.[0].name}}</span> {{/if}} <h2 class=\"post-card-title\">{{#if meta_title}}{{meta_title}}{{else}}{{title}}{{/if}}</h2> </header> <section class=\"post-card-excerpt\"> <p>{{#if meta_description}}{{meta_description}}{{else}}{{excerpt words=\"33\"}}{{/if}}</p> </section> </a> The adjustment above is based on my current confusion (or lack of knowledge) between Excerpt and Meta Description of a Ghost post. For reasons of search engine optimisation (SEO) all my Joomla content already had excessive details in meta title and meta description. The default implementation in the Casper template uses the Excerpt field as article abstract only. The code change above gives priority to an eventually existing meta information and uses excerpt as fall-back. -- tag.hbs {{> \"site-nav\"}} <div class=\"site-header-content\"> <h1 class=\"site-title\"> {{name}} <a class=\"rss-button\" href=\"{{@blog.url}}{{url}}rss/\" target=\"_blank\" rel=\"noopener\">{{> \"icons/rss\"}}</a> </h1> <h2 class=\"site-description\"> {{#if description}} {{description}} {{else}} A collection of {{plural ../pagination.total empty='posts' singular='% post' plural='% posts'}} {{/if}} </h2> </div> Surprisingly, the tag pages in Ghost do not provide easy access to the corresponding RSS feed of a tag. The code change above adds the known RSS feed symbol at the end of tag title. Disqus and the Quest for the lost comments The development team at Ghost added the Disqus integration as a comment into the post.hbs file. Meaning, that one has to uncomment the section and you're ready to go. Well, that's true for a new website but not for a migrated one. In my previous Joomla installation I used a plugin to incorporate Disqus into my blog and it generated a random page identifier some years back. All I had to do was to check the existing request from my old blog to Disqus and adjust the JavaScript page identifier accordingly. {{!-- If you use Disqus comments, just uncomment this block. The only thing you need to change is \"test-apkdzgmqhj\" - which should be replaced with your own Disqus site-id. --}} <section class=\"post-full-comments\"> <div id=\"disqus_thread\"></div> <script> var disqus_config = function () { this.page.url = '{{url absolute=\"true\"}}'; this.page.identifier = '9632ed2b84_id{{comment_id}}'; }; (function() { var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); })(); </script> </section> So, instead of the default value of ghost-{{comment_id}} I had to use my existing page identifier. If you are using Disqus already you have to make this change, and all your comments are successfully linked to your fresh Ghost content. Using Ghost's Code Injection feature I added the following JavaScript fragment to the Blog Footer: <script id=\"dsq-count-scr\" src=\"//getbloggedbyjoki.disqus.com/count.js\" async></script> This enables me to place information about the number of comments of a post anywhere in this blog. Which is eventually interesting on the frontpage as well as the tag overview pages. Recommended attribute in _blank anchors I was kind of surprised to see that the default Casper template doesn't pay attention to proper attribution of hyperlinks that either open a new tab or browser window. Well, actually the attribute rel=\"noopener\" applies to any external link. Open new tabs using rel=\"noopener\" to improve performance and prevent security vulnerabilities. Google's documentation in the Lighthouse developer corner has more information on Opens External Anchors Using rel=\"noopener\". Eventually, I'm going to check the issue tracker of Ghost on GitHub to see whether this has been discussed already. Transferring content to the production system Thanks to the export and import feature in Ghost Admin application this is literally a no brainer. I simply run the export of the content, downloaded my modified Ghost template, and did the upload into the productive instance of Ghost on my external web server. No surprises and everything went as smooth as expected. Existing URLs and rewrites Although Ghost 1.x offers to configure URL redirects in the config.json file I recommend to consult the documentation of your web server, and to add the rules in the server rather than in the software. This improves performance and reduces the load on your Ghost installation. This web site is currently running on nginx, and therefore I added the following rewrite expressions to my server configuration: rewrite ^/blog(/.*)\\.feed(\\?.*)?$ tag/$1/rss/ permanent; rewrite ^/blog(/.*)(/.*)\\.html(\\?.*)?$ $2/$3 permanent; rewrite ^(/.*)\\.html(\\?.*)?$ $1/$2 permanent; rewrite ^/post/index/\\d+(/.*)/?$ $1/ permanent; if ( $args ~ (.*)&id=\\d+:([A-Za-z0-9\\-]+)&.* ) { set $args \"\"; set $arg $2; rewrite (.*) /$arg/ permanent; } try_files $uri.html $uri/ $uri =404; error_page 404 /404/; error_page 500 502 503 504 /500/; The first rule takes care of the previous RSS feeds and maps to the corresponding Ghost tag. The second expression redirects any previous blog articles irrespective their category to an extension-less version of same post in Ghost. And the last RegEx converts any incoming request to an HTML to its extension-less counter-part in Ghost. During the next couple of days I'm going to monitor the error log on the web server to see whether there are more URL redirects needed or not. Also, Google Webmaster Tools is going to help me in that case. An increased number of HTTP 404 entries will surely give me a hint. The other directives provide some fall-back in nginx which allows the server to check for multiple URLs until it finally answers the call with an HTTP 404 in case the content couldn't be matched. Got some ideas from “Hide” .html file extensions using nginx rewrites on serverfault and by an article Remove HTML Extension And Trailing Slash In Nginx Config written by Sean C Davis. SSL upgrade thanks to Let's Encrypt With recent development and increased constraints in regards to safety and search engine ranking I directly seized the opportunity to add SSL certificate to the domain. Nowadays, the easiest and cheapest way is to use Let's Encrypt. The following command does all the hard work $ certbot certonly -d jochen.kirstaetter.name --email user@example.com --agree-tos --standalone The generated certificate needs to be configured in your web server configuration, too. In my case I added the following into the server section: ssl on; ssl_certificate /etc/letsencrypt/live/jochen.kirstaetter.name/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/jochen.kirstaetter.name/privkey.pem; # modern configuration. tweak to your needs. ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; # HSTS (ngx_http_headers_module is required) (15768000 seconds = 6 months) add_header Strict-Transport-Security max-age=63072000; # OCSP Stapling --- # fetch OCSP records from URL in ssl_certificate and cache them ssl_stapling on; ssl_stapling_verify on; Eventually the ciphers might be too harsh as some older browsers are falling off the cliff. Well, time to upgrade then. Qualys SSL Labs gives the current version of this site an A+ rating. Right now, there is only the configuration of CSP and HPKP missing. Something to address within the next couple of days/weeks. Hopefully, my journey to migrate from Joomla 1.x to Ghost 1.x gave you some interesting information and please leave your feedback in the Disqus comments below. I'm relatively new to Ghost and would like to know whether there are more knobs and switches to tweak the experience. Cheers, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'migration-joomla-ghost.html'; this.page.identifier = '9271ed1c97_idmigration-joomla-ghost'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mission-from-god-community-gettogether.html": {
    "href": "mission-from-god-community-gettogether.html",
    "title": "Mission from God - Community GetTogether - Get Blogged by JoKi",
    "summary": "Mission from God - Community GetTogether Copy Markdown View Markdown GitHub Markdown Source Mission from God - Community GetTogether Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mission-from-god-community-gettogether'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mission-from-god-community-gettogether'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Mission from God - Community GetTogether') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-09-02 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Cool, die offizielle Einladung zum nächsten CLIP Community GetTogether hat diese Tage den Weg ins Postfach gefunden. Klasse! Seit dem letzten Treffen auf der CeBIT dieses Jahr sind wieder etliche Ereignisse ins Land gezogen und ich muss offen und ehrlich gestehen, dass ich bedingt durch den Job in den letzten Wochen nur wenig Zeit für Community aufbringen konnte. Dies resultierte sogar darin, dass der monatliche VFP Stammtisch zweimal ohne mich stattfand. Leute, ich gelobe Besserung. So wie es aktuell aussieht, ist die zeitliche Planung des GetTogethers auf einen halben Tag angesetzt. Das ist ehrlich gesagt bedauerlich, denn es stellt sich hierbei ganz klar die Frage nach der Rentabilität. Lohnt es sich für einen halben Tag nach München zu fahren? Nun, ich für meinen Teil kann behaupten: Ja! Denn eine Fahrt nach München bedeutet für mich nicht nur CLIP Community-Treffen sondern immer auch die Möglichkeiten mich mit Studienkollegen und Freunden zu treffen. Locker einen drauf machen und die Zeit einfach nur geniessen. Auch ist unser zweites Büro nur eine Stunde von München entfernt und da lässt sich sicherlich einiges miteinander kombinieren. Der Termin ist auf alle Fälle bereits gesetzt und ich lass' mich überraschen, ob der künftigen Informationen und Ereignisse. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mission-from-god-community-gettogether.html'; this.page.identifier = '9271ed1c97_idmission-from-god-community-gettogether'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "modified-hp-router.html": {
    "href": "modified-hp-router.html",
    "title": "Modified HP Router - Get Blogged by JoKi",
    "summary": "Modified HP Router Copy Markdown View Markdown GitHub Markdown Source Modified HP Router Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'modified-hp-router'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'modified-hp-router'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Modified HP Router') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-01-30 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); While looking for some improvements of Joomla! native SEF engine I found the following extension quite interesting and easy to use: HP Router of Hannes Papenberg. Installation is straight forward and no struggles at all. For best results you might install this extension before starting any kind of search engine optimization for your website. But after a short time I was confronted with some 'nasty' behaviour in my website that was introduced by this extension. Some articles did not show up properly and ended in an HTTP 404 error message. Well, actually reading the description of HP Router gave me an idea of the problem: ATTENTION!! There are a few things you have to keep in mind with this plugin: You can't have the same alias for two articles, even though they are in different categories. To be precise, you can't have the same alias for any type of content item. As a rule of thumb: If you can get to the list of this type of content items from the administrator menu, you can't use the same alias on two items in that list. My 404 problem is directly caused by duplicated aliases... but not the obvious way! The reason here is that the extension (as of writing this article) does not respect the state of the articles in the Joomla! CMS. And due to migration processing from previous blog databases I had a bunch of duplicated topics but deleted. This refers to state -2 instead of published content that has a state of 1. To correct this problem it is necessary to modify two PHP files of the original HP Router: hprouter/com_contentrouter.php hprouter/com_contactrouter.php Almost of the end of each file there are the SQL statements to query the database. After you made a backup of your two files you should change them like so: $db =& JFactory::getDBO(); if($vars['view'] == 'article') { $query = 'SELECT id FROM #__content WHERE **state = 1 AND** alias = '.$db->Quote($vars['id']); } elseif($vars['view'] == 'category') { Modified version of hprouter/com_contentrouter.php $db =& JFactory::getDBO(); $query = 'SELECT id FROM #__contact_details WHERE **state = 1 AND** alias = '.$db->Quote($vars['id']); Modified version of hprouter/com_contactrouter.php With those modifications the HP Router works as expected and queries only published articles. Currently, I am not using the archive state of articles but in case that you will, just extend those two queries to include the necessary state values. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'modified-hp-router.html'; this.page.identifier = '9271ed1c97_idmodified-hp-router'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "modified-mod-lca.html": {
    "href": "modified-mod-lca.html",
    "title": "Modified Mod LCA - Get Blogged by JoKi",
    "summary": "Modified Mod LCA Copy Markdown View Markdown GitHub Markdown Source Modified Mod LCA Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'modified-mod-lca'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'modified-mod-lca'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Modified Mod LCA') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-01-23 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Joomla! content management system (CMS) is great for websites of any type in general. Even using the system as a blog software is quite easy to configure. Instead of the classical Frontpage Layout as default you configure the system to use one of the available Blog Layouts as default page. Then some tweaks according to leading articles, columns, etc. and you are almost done. Almost... One of the great features that I like with blog application is the ability to display an Archive list of existing articles based on their creation date. Well, Joomla!s own archive functionality works in a similar way but sadly I experienced own mayor drawback: Archived articles are not part of the internal search engine anymore and therefore results are not displayed. After my usual online research I found a recently added Joomla! extension called Mod LCA of JoniJnm. The description gives the direct motivation / link to other blog applications: Mod LCA shows articles sorted by year and month, such as wordpress or blogspot The module itself is really easy to install and works as expected. Almost... In case that your content is created the usual way, Mod LCA is just fine. But in my special case I did some direct data migrations via SQL statements from my previous blog software and therefore my sequence of itemids differs from the original creation date of an article. It was quite funny to see that an article of June 2006 was listed in December 2009 by Mod LCA. But hey, when does it happen that the ID and the creation date are controversal? Not quite often but it can happen like in case. Alright, having a look at the source code files of Mod LCA gives a direct glue about how we can improve the situation. At least, it's nothing complicated but the correct order clause of a SQL statement, or? And there we go... Before any modifications be aware to have a backup at hand. Open the file mod_lca/helper.php with your favourite text editor and change the query statement like so: $query = 'SELECT a.id, a.title, a.alias, a.catid, a.sectionid, c.alias as calias, '. $created. ' FROM #__content AS a'. ' LEFT JOIN #__categories AS c ON c.id=a.catid'. ' LEFT JOIN #__sections AS s ON s.id=a.sectionid'. ' WHERE ( a.state = 1 AND s.id > 0 )' . ' AND ( a.publish_up = '.$db->Quote($nullDate).' OR a.publish_up <= '.$db->Quote($now).' )'. ' AND ( a.publish_down = '.$db->Quote($nullDate).' OR a.publish_down >= '.$db->Quote($now).' )'. ' AND s.published = 1'. ' AND c.published = 1'. ' ORDER BY **a.created DESC**'; $db->setQuery($query); ... some lines down ... $out[$d[0]][$month][] = '<a href=\"'.$link.'\">'.**htmlspecialchars(**$row->title**)**.'</a>'; Modified code of mod_lca/helper.php The original code orders the result set by a.id and this might cause problems. With this minor code change articles are queried in the expected order and as a consequence in my case my 'Article Time Line' in the sidebar is properly rendered and displayed. After running some validation tests according to XHTML 1.0 compliance I did some more modifications to mod_lca. By adding the PHP function htmlspecialchars() to encode the output of the article title you will get properly encoded special characters in your hyperlinks. This modification has to be done in the helper.php file as you can see above. To create the tree hierarchy of the article list based on year and month, mod_lca needs some CSS and JavaScript instructions. Well, for the JavaScript it is not a problem itself but the CSS styles should not be placed with the <body> tag of the rendered page. To improve this situation it is necessary to tidy some code of mod_lca. if (!DEFINED(\"LCA_HEADER\")) { // If cache is enabled, we can't add css neither js files define(\"LCA_HEADER\", 1); echo ' **(<style> section deleted)** <script type=\"text/javascript\"> Modified code of mod_lca/tmpl/default.php The key of this solution is to move the style information away from the module and into the template CSS file or if you prefer in its own CSS file. To reduce the number of HTTP requests the styles should be in the template.css file. /******** mod_LCA ********/ li.lca { display: block } ul.lca { padding: 0px } span.lca { cursor: pointer } Modified CSS code of template.css The consequence of those slight modifications is that XHTML validation of my site produces roughly 30 errors less than before. Depending on your content this may vary, of course... \uD83D\uDE09 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'modified-mod-lca.html'; this.page.identifier = '9271ed1c97_idmodified-mod-lca'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "modified-mod-paypal.html": {
    "href": "modified-mod-paypal.html",
    "title": "Modified Ultimate Paypal Donations Module - Get Blogged by JoKi",
    "summary": "Modified Ultimate Paypal Donations Module Copy Markdown View Markdown GitHub Markdown Source Modified Ultimate Paypal Donations Module Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'modified-mod-paypal'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'modified-mod-paypal'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Modified Ultimate Paypal Donations Module') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-02-17 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The article describes some opinions and modifications for a Joomla! extension called Ultimate Paypal Donations Module by JoomlaSpan. First of all I have to say that using this extension to provide any kind of PayPal donation element on your site is really easy and straight forward. You just install it, setup your parameters and mdoule location and you are done. It could not be better... Well, it could. Actually, my main problem with this module is about XHTML 1.0 compliance. Sadly to see that the current version (as of writing this article) does not follow the W3C rules about XHTML 1.0. Luckily, the necessary changes are very simple to realize by modfying one PHP file. Following is the modified version: <form action=\"https://www.paypal.com/cgi-bin/webscr\" method=\"post\"> <input type=\"hidden\" name=\"cmd\" value=\"_donations\" **/**> <input type=\"hidden\" name=\"business\" value=\"<?php echo $paypal_emailID; ?>\" **/**> <input type=\"hidden\" name=\"item_name\" value=\"<?php echo $item_name; ?>\" **/**> <?php if ($item_number) { echo \"<input type=\\\"hidden\\\" name=\\\"item_number\\\" value=\\\"\" . $item_number . \"\\\" **/**>\\r\\n\"; } ?> <?php **if ($amount)** { echo \"<input type=\\\"hidden\\\" name=\\\"amount\\\" value=\\\"\" . $amount . \"\\\" **/**>\\r\\n\"; echo \"<input type=\\\"hidden\\\" name=\\\"lc\\\" value=\\\"\" . $location . \"\\\" **/**>\\r\\n\"; } ?> <input type=\"hidden\" name=\"no_shipping\" value=\"0\" **/**> <input type=\"hidden\" name=\"no_note\" value=\"1\" **/**> <input type=\"hidden\" name=\"currency_code\" value=\"<?php echo $currency_code; ?>\" **/**> <input type=\"hidden\" name=\"tax\" value=\"0\" **/**> <input type=\"hidden\" name=\"bn\" value=\"PP-DonationsBF\" **/**> <?php if ($image_choice==1) { echo \"<input type=\\\"image\\\" **border=\\\"0\\\"** src=\\\"\" . $donate_image . \"\\\" name=\\\"submit\\\" alt=\\\"PayPal - The safer, easier way to pay online!\\\" **/**>\\r\\n\"; }else{ echo \"<input type=\\\"image\\\" **border=\\\"0\\\"** src=\\\"\" . $own_donate_image . \"\\\" name=\\\"submit\\\" alt=\\\"PayPal - The safer, easier way to pay online!\\\" **/**>\\r\\n\"; } ?> </form> Modified version of mod_paypal-j15/tmpl/default.php Despite the tooltip description about choosing your own PayPal button for this module you do not need to specify the full URL including https:// prefix. As you can see in the PHP code any relative path is working too. For reduced number of DNS lookups you should get your preferred PayPal button on your server and change the module to use your 'own' image. In my changes above you also see a marked if-statement in the code. Well, in the original code there are two identical statements, I just merged them together for better reading. Add last but not least, I removed the border attribute from input tags that displays the PayPal image on your site. Style information like border attributes belong to CSS files and should not be part of the HTML code. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'modified-mod-paypal.html'; this.page.identifier = '9271ed1c97_idmodified-mod-paypal'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "more-content-for-mcsds-web-applications-and-windows-store-apps-using-html5.html": {
    "href": "more-content-for-mcsds-web-applications-and-windows-store-apps-using-html5.html",
    "title": "More content for MCSDs: Web Applications and Windows Store Apps using HTML5 - Get Blogged by JoKi",
    "summary": "More content for MCSDs: Web Applications and Windows Store Apps using HTML5 Copy Markdown View Markdown GitHub Markdown Source More content for MCSDs: Web Applications and Windows Store Apps using HTML5 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'more-content-for-mcsds-web-applications-and-windows-store-apps-using-html5'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'more-content-for-mcsds-web-applications-and-windows-store-apps-using-html5'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'More content for MCSDs: Web Applications and Windows Store Apps using HTML5') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-02-18 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); This article is a follow-up to Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5 and I am going to focus a bit on learning material for the second exam 70-481 Essentials of Developing Windows Store Apps using HTML5 and JavaScript. Step by step towards MCSD The general motivation is there, first exam 70-480 has been passed successfully, and there is more the queue during the upcoming weeks. Compared to exam 70-480 the second one doesn't offer so much credits but let's have a quick look at them: Credit Toward Certification Exam 70-481: Essentials of Developing Windows Store Apps using HTML5 and JavaScript: counts as credit toward the following certification(s): MCSD: Windows Store Apps using HTML5 As you can see, nothing special just consequent continuation. Preparation and learning material this time Frankly speaking, there isn't much to add in this case and the already referenced resources are still your best bet and bang for money (read: as good as free). I'll give you condensed summary of the ones I'm currently using: Developing Windows Store Apps with HTML5 Jump Start An accelerated preparation video course on Windows 8 Apps. There are six modules with two video sessions each which will catapult you to your exam. This is also related to preps for exam 70-481. Windows 8 Design handbook It is actually not an official resource by Microsoft but still it explains the major details in a very appealing and informative way. Programming Windows 8 Apps with HTML, CSS, and JavaScript Kraig Brockschmidt delves into the ups and downs of Windows 8 App development over 800+ pages. Great eBook to read, study, and to practice the samples - best of all, it's for free. Read my review on this title. codeSHOW() This is a Windows 8 HTML/JS project with the express goal of demonstrating simple development concepts for the Windows 8 platform. Code, code and more code... absolutely great stuff to study and practice. Microsoft Virtual Academy I already wrote about the MVA in a previous article. Well, if you haven't registered yourself yet, now is the time. Pluralsight online learning Windows 8 Store Apps Hands-on in 20 Lessons Testable Windows 8 Applications Windows 8 Background Processing and Lifecycle Management Building Windows 8 Applications with JavaScript and HTML The videos are actually great resources to get you up to speed, and on of the best options while you commute between home and work. Then intensify and deepen your knowledge with Kraig's book, and analyse and practice with the samples from codeShow(). This can't be wrong... Don't be a lonely wolf: Start/join a learning group Sorry but I repeat myself here... The way you learn new things or intensify your knowledge in a certain technology is completely up to your personal preference. Back in my days at the university, we used to meet once or twice a week in a small quiet room to exchange our progress, questions and problems we ran into. In general, I recommend to any software craftsman to lift your butt and get out to exchange with other developers. Personally, I like this approach, as it gives you new points of view and an insight into others' own experience with certain techniques and how they managed to solve tricky issues. Just keep it relaxed and not too formal after all, and you might a have a good time away from your dull office desk. Give your machine a break, too. Additional benefits for your employer or company Just in case that either your employer or your own company is registered as Microsoft Partner I would like to mention that by passing the exams of this MSCD certification you are eligible for various competencies. Only by passing the exam 70-480 successfully my company IOS Indian Ocean Software Ltd. engaged to complete the following Silver competencies: Application Development Digital Marketing Mobility By achieving at least one Silver Competency your business or employer is eligible to upgrade to Microsoft Silver Partner which enables you to use their prestigious logos on your website and gives you access to exclusive member resources. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'more-content-for-mcsds-web-applications-and-windows-store-apps-using-html5.html'; this.page.identifier = '9271ed1c97_idmore-content-for-mcsds-web-applications-and-windows-store-apps-using-html5'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mra-mail-hostname.html": {
    "href": "mra-mail-hostname.html",
    "title": "Dear Postmaster of Mauritius Revenue Authority - Get Blogged by JoKi",
    "summary": "Dear Postmaster of Mauritius Revenue Authority Copy Markdown View Markdown GitHub Markdown Source Dear Postmaster of Mauritius Revenue Authority Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mra-mail-hostname'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mra-mail-hostname'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Dear Postmaster of Mauritius Revenue Authority') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-10-03 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Please review the current host name pattern given to your internet-facing mail server(s). At least one of them doesn't comply with RFC2821 - section 4.1.2. This results in delivery issues of your emails. The Mauritius Revenue Authority (MRA) is a body corporate, set up to manage an effective and efficient revenue-raising system. It administers and collects taxes due in Mauritius within an integrated organisational structure. The MRA is an agent of State and, as such, the Ministry of Finance and Economic Development continues to have overall responsibility for the organisation and monitors its performance. The MRA is responsible for collecting approximately 90% of all tax revenues and for enforcing tax laws in Mauritius (Taken from their About MRA page). Annual filing of individual tax return Filing your annual returns and paying taxes is as certain as death, and absolutely welcoming to contribute to the economics of a country. It's a citizen's duty to handle this with care and in due time. The MRA is doing a great job by offering their e-Services like the E-Filing of Individual Tax Return Year of Assessment 2017-2018. Submitting your tax return happens online, and given their confirmation email one would pay (if applicable). And this is where my or better said our current problem starts. Entering all related information and submitting the data to the MRA wasn't a problem after all but it's the situation that you are supposed to receive an email with details about processing identification and the amount to pay. We never received such an email. Where's the email from MRA? Although our tax filing happened prior to due date we noticed that neither my wife nor I actually received an email with that processing ID number or details about necessary payments. First reaction in such cases would be to check whether the mail client had a good day and accidentally classified such an email as SPAM and therefore might have moved it into other folder. But no traces were found in our mail software. Okay, accepting the circumstances that the email might have not been delivered to the client I went to check our mail server infrastructure. Searching through the log files on our internal groupware with mail sub-system didn't give any clues in regards to MRA as sender. Next, I went further and started to look into the recent and rotated log files on our external mail relay server. Using a command line expression like this: $ zgrep mra /var/log/exim4/* I finally got some hints what might have happened: /var/log/exim4/rejectlog.5.gz:2017-09-27 15:14:44 rejected EHLO from mail.mra.mu [196.27.73.162]: syntactically invalid argument(s): mra_esa1.mra.mu /var/log/exim4/rejectlog.5.gz:2017-09-27 15:16:12 rejected HELO from mail.mra.mu [196.27.73.162]: syntactically invalid argument(s): mra_esa1.mra.mu ... /var/log/exim4/rejectlog.2.gz:2017-09-30 16:15:03 rejected EHLO from mail.mra.mu [196.27.73.162]: syntactically invalid argument(s): mra_esa1.mra.mu /var/log/exim4/rejectlog.2.gz:2017-09-30 16:16:31 rejected HELO from mail.mra.mu [196.27.73.162]: syntactically invalid argument(s): mra_esa1.mra.mu Obviously, our mail transfer agent (MTA) didn't like some information during the initial communication establishment with the mail server of the MRA. Positive note: The mail server of the MRA seems to have an email delivery policy of approximately four attempts per hour over a maximum period of 72 hours. What is causing the reject? Firing up my favourite search engine lead me to a mailing list thread Re: [exim] syntactically invalid argument(s) from November 2004. At first reading I got the impression that I had a mishap in my configuration but then it turns out that an underscore character in the host name seems to be the culprit of this (scroll the log entries to the right if needed). Not surprising I found a \"solution\" on stack overflow which suggests to ease exim's strict syntax checking a little bit and to allow broken systems to send email: echo 'helo_allow_chars = _' > /etc/exim4/conf.d/main/04_exim4-helo_hack And documentation of exim on helo_allow_chars gives the following information: \"This option can be set to a string of rogue characters that are permitted in all EHLO and HELO names in addition to the standard letters, digits, hyphens, and dots. If you really must allow underscores, ...\" Violation of RFC2821, section 4.1.2 Given the nature of rogue characters I went further to see what the Request for Comments: 2821 - Simple Mail Transfer Protocol has to offer. Searching the document for underscore lands you in section 4.1.2 Command Argument Syntax where it says the following: To promote interoperability and consistent with long-standing guidance about conservative use of the DNS in naming and applications (e.g., see section 2.3.1 of the base DNS document, RFC1035 [22]), characters outside the set of alphas, digits, and hyphen MUST NOT appear in domain name labels for SMTP clients or servers. In particular, the underscore character is not permitted. SMTP servers that receive a command in which invalid character codes have been employed, and for which there are no other reasons for rejection, MUST reject that command with a 501 response. That's highly interesting - In particular, the underscore character is not permitted. -, as the RFC explicitly classifies the underscore as an invalid character. And having a look at the referred section 2.3.1. Preferred name syntax of RFC1035 provides more details on the naming pattern of host names: The labels must follow the rules for ARPANET host names. They must start with a letter, end with a letter or digit, and have as interior characters only letters, digits, and hyphen. There are also some restrictions on the length. Labels must be 63 characters or less. Again, no mentioning of the underscore character. Public rant and comments Not happy about not receiving such an important email as the confirmation of your annual tax return and payment instructions based on a mistake that could be avoided easily, I posted on Twitter about it. Oh fantastic! The mail server configuration of @MRA_services violates hostname convention in RFC 2821 - 4.1.2 - Underscore is NOT permitted! — Jochen Kirstätter (@JKirstaetter) October 2, 2017 Later on, this was picked up by other internauts. Mr Moonesamy asked for permission to quote me in a note to the Mauritius Revenue Authority. Can I reference your tweet in a message to the MRA? #mauritius — S Moonesamy (@sminmu) October 2, 2017 And Pirabarlen noted that other mail providers are violating RFCs, too. I think you guys are forgetting/ignoring that Microsoft and google doesn't even respect rfc 5321 correctly. — Pirabarlen (@eldergodselven) October 2, 2017 First of all, I have to clarify that I'm not forgetting/ignoring that other companies or mail service providers might not respect RFCs at all. Second, I'm also aware that RFC clearly stands for Request for Comments and therefore is rather a recommendation than a regulation. Especially big internet companies have this tendency to run for their own ideas regularly. Much to the disadvantage of others in most cases. And third, I can't accept a statement like \"How about others doing...\" as a justification or reasoning to do wrong. As regular submitter to the Mauritius Internet User (MIU) mailing list, Ish Sookun started a thread on Mauritius Revenue Authority violates hostname convention, and provides more information on the SMTP mail header as he actually received the confirmation email. Gratefully, as mentioned by Pirabarlen, Ish also refers to RFC5321 - Section 4.1.2 which stipulates that \"characters outside the set of alphabetic characters, digits, and hyphen MUST NOT appear in domain name labels for SMTP clients or servers.\" The underscore character is not part of that definition. Eventually, it might be that I'm an isolated case in regards to having issues with the confirmation email of the Mauritius Revenue Authority but there might be other tax payers affected by this. Seeing something as simple as a host name which doesn't comply with RFCs leaves a bad taste there might be other services configured wrongly. What else to notice... Eventually, you might have noticed the difference of host name in the log entries above - mail.mra.mu vs. mra_esa1.mra.mu. Let's do a couple of DNS lookups to see what's going on here actually. I use the dig command line tool on a Linux system to query DNS information. Eventually, you might consider to use an online service like MX Toolbox. On Windows you can execute the nslookup command in either a Command Prompt or PowerShell. See Nslookup reference on TechNet for syntax. Talking about mail server I'm having a look at the MX record of the mra.mu domain first. $ dig -t mx mra.mu Which gives me the expected information about mail.mra.mu as the properly configured mail exchange point of the domain. Next, let's get some details about that particular system. $ dig mail.mra.mu Again, as expected the A record provides the IP address logged above. So, what's the fuzz about the host name with the underscore? $ dig mra_esa1.mra.mu Turns out that the name is not configured publicly after all. Meaning that the connecting mail server actually uses a host name not matching its IP address and public DNS record to establish communication. Just in case you ask yourself what the suffix \"esa\" might stand for. A wild guess could be that the MRA is using an appliance like the Cisco C370 Email Security Appliance. What about the PTR record of the IP address? A Pointer (PTR) record resolves an IP address to a fully-qualified domain name (FQDN) as an opposite to what A record does. PTR records are also called Reverse DNS records. PTR records are mainly used to check if the server name is actually associated with the IP address from where the connection was initiated. $ dig -t ptr 196.27.73.162 Unfortunately, it seems that Reverse DNS and Pointer record has not been set up. It's not compulsory to have a PTR record but it would increase the level of authenticity of the origin of the sender. And speaking of sender information... Lack of Sender Policy Framework (SPF) and DKIM The domain mra.mu does not provide any commonly used safety features on the origin and therefore authenticity of the sender of an email. Querying DNS to get configuration on Sender Policy Framework (SPF) or DomainKeys Identified Mail (DKIM) doesn't show any data. $ dig -t txt mra.mu $ dig -t spf mra.mu Given the current state of analysis I honestly doubt that DNS Security Extensions (DNSSEC) is in place. Deep concerns about this I don't know about you but seeing this lack of consciousness on security related aspects, it might be trivial for an expert in the field to forge and exploit this. Especially for such an important institution as the Mauritius Revenue Authority which is responsible for collecting approximately 90% of all tax revenues, I'm deeply concerned. What started as a \"Quest to find a lost email\" escalated quickly into a major security concern. Maybe \"Dear IT department of Mauritius Revenue Authority\" might have been a better title for this article. Please leave your thoughts and experience on this case, and hopefully similar ones in the comments section below. Computer name on Windows (10) Challenged by Pirabarlen's statement regarding other companies like Microsoft or Google disrespecting RFC 5321, I tried to run a little experiment on my local Windows 10 machine. Rename your PC using an underscore character. It didn't go well... Seems that Microsoft learned their lesson and improved their product(s). At least in compliance to host name. Why isn't there a similar input validation for host name configuration in the software or appliance used by the MRA? Would be interesting to find out... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mra-mail-hostname.html'; this.page.identifier = '9271ed1c97_idmra-mail-hostname'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-14122013-christmas-raffle-plans-2014.html": {
    "href": "mscc-14122013-christmas-raffle-plans-2014.html",
    "title": "MSCC: Christmas raffle and plans for 2014 - Get Blogged by JoKi",
    "summary": "MSCC: Christmas raffle and plans for 2014 Copy Markdown View Markdown GitHub Markdown Source MSCC: Christmas raffle and plans for 2014 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-14122013-christmas-raffle-plans-2014'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-14122013-christmas-raffle-plans-2014'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Christmas raffle and plans for 2014') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-12-17 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Our last monthly meetup for 2013 had been organised at the Ebene Accelerator, Orange Tower, Ebene. Already the pre-registration or commonly known the RSVPs look very promising. At least 17 craftsmen were expected to show up. And... it was better than that. We cracked down the 20-people mark and had a total of 23 craftsmen (and -women) in the conference room(s) at the Ebene Accelerator, again a good number of 'first-timers'. If this trend is going to continue we have to see for another location with a bigger room. Soon... As in previous articles I'm going through the various topics and events that happened on that occassion. But again, let others speak first... Reactions from other craftsmen Actually, it was Ish that came up with a fantastic idea. Any winner of our Christmas raffle has to write about the gathering, and it went well... \"The exchange of ideas with professionals was enriching, business and research wise. The members seem to master the knowledge in their fields.\" -- Irfaan on The first meeting MSCC \"As part of the deal to get free access to tons of online videos, I’ve accepted to blog, under my own terms Today I went to the MSCC, and I met a bunch of software developers, and saw a bunch of familiar faces that I will decline to name for their own safety.\" -- sysadmin on First MSCC meeting \"As expected, today's meetup was full and met with new peeps. That's great the group is growing even bigger.\" -- Ibraahim on MSCC meetup - last one for 2013 \"MSCC is celebrating 6 months; to have 67 registered craftsmen & more than 20 attending a meetup is indeed a success. We hope to grow bigger, better & continue sharing the love for code!\" -- Ish on MSCC meetup - Christmas raffle It is really great to receive this feedback. It's like fuel to your own actions and it drives me to push our community further and further. I already put a short summary of our activities and achievements in 2013 in this article \"How's Mauritius Software Craftsmanship Community going?\" Registration of newcomers As stated, there have been a good number of newbies at the recent meetings and once again I explained the importance of 'proper' registration on our official community website on meetup.com. First of all, it's always nice to have a kind of a Yellow Pages about the Who's who in our group but most important for me... The number of actively registered craftsmen - 70 as of writing this article - is of great value towards other companies, institutions and governmental bodies. We, as the members of the MSCC, represent a community of like-minded people in the IT world of Mauritius. Which means that we are able to facilitate information and channel activities with a certain background and content. Compared to a simple 'Like' or a 'Plus' on a social media network our craftsmen willingly joined this community and provide some information about themselves, too. Despite the DRY principle: If you are not registered yet, please join the MSCC community (for free) and support our work by your presence. Thanks! Together, we can reach 100+ craftsmen easily... How was Infotech and Linuxfest 2013? Even though I gave two presentations at the Infotech 2013 and the Linuxfest 2013 I had to rush off for a business trip to Europe. So, I asked Ish to give us a brief status update and feedback about the events that happened during and after both acts. One of the interesting aspects, in my humble opinion, is the fact that we - the MSCC as well as the LUGM - are on the radar of the National Computer Board (NCB). According to Ish they seem to consider our knowledge for upcoming events, and overall that is absolutely awesome. Furthermore, as we a growing community we are collecting more momentum and 'force' to actually move and make things happen. Already last month, Yannick and I went out to requests appointments with local IT companies in order to create awareness about the existence and whereabouts of the MSCC. We are going to continue this during the next couple of weeks and months. Planning for 2014 There are multiple ideas and concepts currently going on for 2014... More locations in different regions of the island. I spoke about this in earlier meetings already but I would really love to see this happening. At the moment, I have potential locations in Quatre Bornes and at Pierrefonds which would be suitable for one of our monthly meetups. Personally, I would like to be able to organise several meetups in Port Louis, Grand Baie, Flacq and Mahebourg (among others of course). Setting up 'study groups' This is based on feedback from our craftsmen. Personally, I like to talk about software concepts and technologies and forming a (or multiple) study groups on various topics would be great. A driving force could be to aim for professional IT certification, like Microsoft exams, ie. Microsoft Certified Software Developer (MSCD), or similar. Interestingly, we already have the necessary literature and eLearning requirements in our hands thanks to Pluralsight and Microsoft Virtual Academy (MVA). Organising a Developer's Conference That would be a dream come true... Honestly, I'm missing the possibility to learn about new technologies, to get expert tips and tricks on latest software solutions, and having a vital exchange of experience with other IT people. Extremely! Despite a large number of IT companies in the outsourcing sector there is not a single kind of event that would be close to a developer's conference. I mean there are so many opportunities but still absolutely nothing available on this beautiful island. That has to be changed. The MSCC is going to make 'Cyber Island' a reality! Jobs and project offers Thankfully, I had a good conversation with Mrs Poonum - she's responsible for the Ebene Accelerator - prior to our meetup, and she updated me with some very interesting information about an increasing number of projects and job offers through the accelerator itself. Young startups and entrepreneur are highly advised to get in touch with her to talk about potential work - obviously this seems to be a long-term base. And a little bit selfish... This time I was looking for potential candidates for my own business - IOS Indian Ocean Software Ltd. - in order to be able to keep the deadlines on projects with existing clients and to cover the increasing demand on new projects. I also seized the opportunity to have a quick chat with another craftsman on a short-term freelance cooperation. Let's see whether I'll be able to up-size my team in January or February. Raffle of some small goodies And last but not least, it was time to give out the goodies... Following a list of prizes: 1 USB hub and extension 4 One month subscription of Pluralsight 1 One Year subscription of Pluralsight 1 Office 365 Home Premium license My sincere Thank You goes out to our sponsors, namely Pluralsight, IOS Indian Ocean Software Ltd., and (indirectly) Microsoft. The raffle itself was already a very cool demonstration of our craftsmen, while it was decided that the main prize should be randomly selected, a bunch of coders sat immediately down to write a couple of lines in Python. Meanwhile, Ish and I came up with a couple of questions from the open source sector: Who was/is the responsible developer behind MySQL? What's the name origin of MySQL and MariaDB? How old (in terms of years) is JavaScript? When was the MSCC founded? Anyway, we had great fun and finally the grand prize of our 2013 Christmas raffle was selected by 'rando.py' in the third attempt. Neither Ish nor Pritvi were eager to go ahead with an Office 365 license. And finally we had a female MSCC member to receive the prize. Congratulations! My resume of the day \"Despite a little bit of chaos it was a relaxed and funny meetup today.\" - Yes, indeed it was a bit chaotic with several independent conversations going on, having a great discussion on software piracy, patent handling and entrepreneurship. An IT community in Mauritius is possible - and we are living it! Thank you so much, dear craftsmen. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-14122013-christmas-raffle-plans-2014.html'; this.page.identifier = '9271ed1c97_idmscc-14122013-christmas-raffle-plans-2014'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-20140628-version-control-systems-git-tfs-vso.html": {
    "href": "mscc-20140628-version-control-systems-git-tfs-vso.html",
    "title": "MSCC: Purpose and benefits of Version Control Systems (VCS) - Get Blogged by JoKi",
    "summary": "MSCC: Purpose and benefits of Version Control Systems (VCS) Copy Markdown View Markdown GitHub Markdown Source MSCC: Purpose and benefits of Version Control Systems (VCS) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-20140628-version-control-systems-git-tfs-vso'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-20140628-version-control-systems-git-tfs-vso'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Purpose and benefits of Version Control Systems (VCS)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-06-29 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Unfortunately, there was no monthly meetup during May. Which means that it was even more important and interesting to go forward with a great topic for this month. Earlier this year I already spoke to Nayar Joolfoo about doing a presentation on version control systems (VCS), and he gladly agreed since then. It was just about finding the right date for the action. Furthermore, it was also a great coincidence that Avinash Meetoo announced on social media networks thatKnowledge 7 is about to have a new training on \"Effective git\" - which correlates to a book title Avinash is currently working on - all the best with your approach on this and reach out to our MSCC craftsmen for recessions. Once again a big Thank you to Orange Ebene Accelerator on providing the venue for us, and the MSCC members involved on securing the time slot for our event. Unfortunately, it's kind of tough to get an early confirmation for our meetups these days. I'll keep you posted on that one as there are some interesting and exciting options coming up soon. Okay, let's talk about the meeting and version control systems again. As usual, I'm going to put my first impression of the meetup: \"Absolutely great topic, questions and discussions on version control systems, like git or VSO. I was also highly pleased by the number of first timers and female IT geeks. Hopefully, we will be able to keep this trend for future get-togethers.\" And I really have to emphasise the amount of fresh blood coming to our gathering. Also, during the initial phase it was surprising to see that exactly those first-timers, most of them students at various campuses here on the island, had absolutely no idea about version control systems. More about further down... Reactions of other attendees If I counted correctly, we had a total of 17 attendees this month, and I'd like to give you feedback from some of them: \"Inspiring. Helped me understand more about GIT.\" -- Sean on event comments \"Joined the meetup today with literally no idea what is a version control system. I have several reasons why I should be starting to use VCS as from NOW in my projects. Thanks Nayar, Jochen and other participants \uD83D\uDE03\" -- Yudish on event comments \"Was present today and I'm very satisfied.I was not aware if there was a such tool like git available. Thanks to those who contributed for this meetup. It was great. Learned a lot from this meetup!!\" -- Leonardo on event comments \"Seriously, I can see how it’s going to ease my task and help me save time. Gone are the issues with files backups. And since I’ll be doing my dissertation this year, using Git would help me a lot for my backups and I’m grateful to Nayar for the great explanation.\" -- Swan-Iyah on MSCC meetup : Version Controls Hopefully, I'll be able to get some other sources - personal blogs preferred - on our meeting. Geeks, thank you so much for those encouraging comments. It's really great to experience that we, all members of the MSCC, are doing the right thing to get more IT information out, and to help each other to improve and evolve in our professional careers. Our agenda of the day Honestly, we had a bumpy start... First, I was battling a little bit with the movable room divider in order to maximize the space. I mean, we had 24 RSVPs and usually there might additional people coming along. Then, for what ever reason, we were facing power outages - actually twice in short periods. Not too good for the projector after all, but hey it went smooth for the rest of the time being. And last but not least... our first speaker Nayar got stuck somewhere on the road. \uD83D\uDE09 Anyway, not a real show-stopper and we used the time until Nayar's arrival to introduce ourselves a little bit. It is always important for me to get to know the \"newbies\" a little bit, and as a result we had lots of students of university - first year, second year and recent graduates - among them. Surprisingly, none of them was ever in contact with version control systems at all. I mean, this is a shocking discovery! Similar to the ability of touch-typing I'd say that being able to use (and master) any kind of version control system is compulsory in any job in the IT industry. Seriously, I'm wondering what is being taught during the classes on the campus. All of them have to work on semester assessments or final projects, even in small teams of 2-4 people. That's the perfect occasion to get started with VCS. Already in this phase, we had great input from more experienced VCS users, like Sean, Avinash and myself. git - a modern approach to VCS - Nayar What a tour! Nayar gave us the full round of git from start to finish, even touching some more advanced techniques. First, he started to explain about the importance of version control systems as an essential tool for software developers, even working alone on a project, and the ability to have a kind of \"time machine\" that allows you to inspect and revert to a previous version of source code at any time. Then he showed how easy it is to install git on an Ubuntu based system but also mentioned that git is literally available for any operating system, like Windows, Mac OS X and of course other Linux distributions. Next, he showed us how to set the initial configuration values of user name and email address which simplifies the daily usage of the git client while working with your repositories. Then he initialised and added a new repository for some local development of a blogging software. All commands were done using the command line interface (CLI) so that they can be repeated on any system as reference. The syntax and the procedure is always the same, and Nayar clearly mentioned this to the attendees. Now, having a git repository in place it was about time to work on some \"important\" changes on the blogging software - just for the sake of demonstrating the ease of use and power of git. One interesting question came very early: \"How many commands do we have to learn? It looks quite difficult at the moment\" - Well, rest assured that during daily development circles you will need less than 10 git commands on a regular base: git add, commit, push, pull, checkout, and merge And Nayar demo'd all of them. Much to the delight of everyone he also showed gitk which is the git repository browser. It's an UI tool to display changes in a repository or a selected set of commits. This includes visualizing the commit graph, showing information related to each commit, and the files in the trees of each revision. Using gitk to display and browse information of a local git repository And last but not least, we took advantage of the internet connectivity and reached out to various online portals offering git hosting for free. Nayar showed us how to push the local repository into a remote system on github. Showing the web-based git browser and history handling, and then also explained and demo'd on how to connect to existing online repositories in order to get access to either your own source code or other people's open source projects. Next to github, we also spoke about bitbucketand gitlabas potential online platforms for your projects. Have a look at the conditions and details about their free service packages and what you can get additionally as a paying customer. Usually, you already get a lot of services for up to five users for free but there might be other important aspects that might have an impact on your decision. Anyways, moving git-based repositories between systems is a piece of cake, and changing online platforms is possible at any stage of your development. Visual Studio Online (VSO) - Jochen Well, Nayar literally covered all elements of working with git during his session, including the use of external online platforms. So, what would be the advantage of talking aboutVisual Studio Online (VSO)? First of all, VSO is \"just another\" online platform for hosting and managing git repositories on remote systems, equivalent to github, bitbucket, or any other web site. At the moment (of writing), Microsoft also provides a free package of up to five users / developers on a git repository but there is more in that package. Of course, it is related to software development on the Windows systems and the bonds are tightened towards the use of Visual Studio but out of experience you are absolutely not restricted to that. Connecting a Linux or Mac OS X machine with a git client or an integrated development environment (IDE) like Eclipse or Xcode works as smooth as expected. So, why should one opt in for VSO? Well, one of the main aspects that I would like to mention here is thatVSO integrates the Application Life Cycle Methodology (ALM) of Microsoft in their platform. Meaning that you get agile project management with Backlogs, Sprints, Burn-down charts as well as the ability to track tasks, bug reports and work items next to collaborative team chats. It's the whole package of agile development you'll get. And, something I mentioned briefly during the begin of our meeting, VSO gives you the possibility of an automated continuous integrated (CI) process which builds and can run tests of your source code after each commit of changes. Having a proper CI strategy is also part of theClean Code Developer practices - on Level Green actually -, and not only simplifies your life as a software developer but also reduces the sources of potential errors. Seamless integration and automated deployment between Microsoft Azure Web Sites and git repository But my favourite feature is the seamless continuous deployment to Microsoft Azure. Especially, while working on web projects it's absolutely astounishing that as soon as you commit your chances it just takes a couple of seconds until your modifications are deployed and available on your Azure-hosted web sites. Upcoming Events and networking Due to the adjusted times, everybody was kind of hungry and we didn't follow up on networking or upcoming events - very unfortunate to my opinion and this will have an impact on future planning of our meetups. Because I rather would like to see more conversations during and at the end of our meetings than everyone just packing their laptops, bags and accessories and rush off to grab some food. I was hoping to get some information regarding this year's Code Challenge - supposedly to be organised during July? Maybe someone could leave a comment on that - but I couldn't get any updates. Well, I'll keep digging... In case that you would like to get more into git and how to use it effectively, please check out Knowledge 7's upcomingcourse on \"Effective git\". Thanks Avinash for your vital input into today's conversation and I'm looking forward to get a grip on your book title very soon. My resume of the day Do not work in IT without any kind of version control system! Seriously, without a VCS in place you're doing it wrong. It's like driving a car without seat belts attached or riding your bike without safety helmet. You don't do that! End of discussion. \uD83D\uDE09 Nowadays, having access to free (as in cost) tools to install on your machine and numerous online platforms to host your source code for free for up to five users it's a no-brainer to get yourself familiar with VCS. Today's sessions gave a good overview on how to start using git and how to connect to various remote services like github or VSO. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-20140628-version-control-systems-git-tfs-vso.html'; this.page.identifier = '9271ed1c97_idmscc-20140628-version-control-systems-git-tfs-vso'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-20140830-entrepreneurship-and-startup-culture.html": {
    "href": "mscc-20140830-entrepreneurship-and-startup-culture.html",
    "title": "MSCC: Entrepreneurship and start-up culture (in Mauritius) - Get Blogged by JoKi",
    "summary": "MSCC: Entrepreneurship and start-up culture (in Mauritius) Copy Markdown View Markdown GitHub Markdown Source MSCC: Entrepreneurship and start-up culture (in Mauritius) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-20140830-entrepreneurship-and-startup-culture'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-20140830-entrepreneurship-and-startup-culture'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Entrepreneurship and start-up culture (in Mauritius)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-08-31 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After skipping a monthly meetup back in July it was highly anticipated that we are going to get together again during August. And wow, what an experience it has been... Not only the event itself but also the week before, and topics and conversations during the meeting, and the first responses and comments these days. But let's start from the beginning and let me directly announce this: New Record! Yes, we did it once more again, and topped the previous number of attendees. Prior to the meetup we already had 29 member and 3 guest registration, and yesterday we had about 35 IT-loving craftsmen literally cramped into both meeting rooms at the Ebene Accelerator - Thanks again! Quite frankly, it was packed early, and new arrivals were still coming. Awesome! Meetup announcement: Entrepreneurship and start-up culture (in Mauritius) Having the idea of starting your business is always a great opportunity. And with the right concepts, perseverance and consistency of doing things, you will likely be successful. But the path to success is full of obstacles and problems to be solved. Just too often it happens that young people fail in their persuade to glory due to small issues not thought about or misleading information. This month's meetup is about the experience of existing start-ups and recent entrepreneurs here on the island. We are going to have a group of people to report about their journey, to share their knowledge, and to exchange about some nasty issues that might arise on your way to run your own business. Everyone is largely invited to join our community meetup, and get some inspiration, some answers, and maybe also great ideas for new business opportunities on the way. Our guest speakers in order of appearance: Paperboat: Rikesh IOS Indian Ocean Software: Jochen Shortcut Media: Franco Shoponline.mu: Dhiruj Medine Education Village: Christophe Acharis: Santosh Pongo Soft: Vincent & Louis e.venture: Ish As you can see, the meeting was put up with a high emphasis on real world experience and sharing of knowledge. Something surely to talk about and discuss in a room. My first thoughts... Alright, let me quickly note down my first impression of our get-together and then guide you through some of the details: \"It was a very interesting monthly meetup and lots of information, different opinions and serious business advice. It was great to see the variety of obstacles faced and managed... And thanks to our mixture of speakers from Mauritius, Switzerland, India, and indirectly from France and Germany at least I had a good insight on various topics, attitudes and recommendations. Looking forward to a follow up on this topic in the near future... Maybe as an Happy Hour at another premise. \uD83D\uDE09\" Truly, it was a great experience to have this kind of variety in terms of business experience and opinions. I got a lot of new input for my own business at IOS Indian Ocean Software Ltd. and some great advice to follow up during the next couple of weeks. Reactions of other attendees As usual, I'd like to give you feedback from other craftsmen first: \"Oh it was very inspiring, learned new Awesome stuffs and enjoy a lot to discover new opportunities, tips and tricks and most of all I got to increase my networking with new guys with crazy horizon \uD83D\uDE03\" -- Shamsher on event comments \"Great session - I loved the different perspectives presented today.\" -- Dan on event comments \"This community is doing some Great stuffs and I recommend to any developer to check them out. You have everything to gain from it, nothing to lose I was so focused on the presentations that I just couldn't take more pics\" -- Kevin on Facebook \"They [Vincent and Louis] stressed on the facilities provided by Ebène Accelerator and how it was relatively easy to set up a business in Mauritius. As at date they have a lot of Mauritian customers and shared tips that should be useful to those wishing to have their startup at the incubator.\" -- Ish on Talks on Entrepreneurship \"Absolutely awesome guys, very interesting to hear all those opportunities & challenges facing startups in Mauritius.\" -- Parvez on event comments Overall, I’m glad I attended the session. Clearly, the MSCC is gaining traction! I’ve never seen that conference room so full. Soon, we’ll need a new venue. I’ll be inviting some of my colleagues to more of these sessions later on. I highly recommend them. -- Sean on MSCC: Talks on Entrepreneurship Getting such an early (and spontaneous) feedback from our members is just great, and tells me that we really hit a nerve here on the island. The Mauritius Software Craftsmanship Community is steadily growing and until today we have more than 180 registered people. Hopefully, we will be able to cross the 200 before the end of the year. Aspects of entrepreneurship (by experience) Dear reader, the following bullet points are related to the conversations and exchange we had during our meeting and are purely based on subjective experience of each and everyone. I'm trying to sum up some of the more relevant aspects of our various conversations and Q&A sessions. So, please don't take all of this for granted and evaluate some statements clearly as opinions, eventually as some guidance. Now, let the fun begin... Gain some experience as an employee first Through out the bench of guest speakers all of them started as an employee. Except myself, I had to learn it the hard way and went into employment after one year of miserable results in my first company. In general, it is clearly advised by each and every one sharing their experience as a business owner that should get into business as an employee first. See how the real world of labour is ticking, take notes and learn from others experience. Yes, you might argue that it's faster to jump into the pond to learn how to swim but you know there have been cases where people simply drowned. Of course, you should be selective regarding your first job(s) and take care that you're not working for some miserable low salary but your focus should be learning about customer facing situation, how to deal with contracts and learn about negotiations with your superiors or during meetings with clients. If you have a look at the big shots of our time, a huge number of founders, CEOs, and managing positions were employees for a couple of years in the first place. In most cases their own business spun off with an idea left unsatisfied by their employer. And that's something you should take into consideration. A problem to be solved... As just mentioned, a lot of company founders develop a business idea based on an existing, real-world problem which hasn't been addressed by anyone else before (or in a very poor, unsatisfying way). This is even more important to recognise when you're about to develop your business around a product or services compared to freelancing. More about that below. As you might start to work as an employee always keep your eyes open for existing problems. Take customer complains or requests as a source of opportunity. Always remember the following proverb: One man's trash is another man's treasure Discovering, exploiting and solving a problem is much likely a permission to print money. Pay attention to what people are saying regarding their problems and surely you'll find a bunch of ideas and business opportunities. Communication, communication, and... communication As a business owner you have to be customer facing and somehow outgoing. If you're an introvert person, please think twice if that's what you're after. Communication I - You should have the ability to converse with your clients and leads regarding the services and products you're offering in your company. As for speaking to leads you have to be focus on the pitch and be spot-on right about the terms you use. This also depends on the type of audience you're addressing but usually it involves to leave the tech-talk in the lowest drawer at home. Know and learn how to speak business and seize all kind of opportunities to get in touch with people. Keep your perseverance and don't get discouraged on denials. Always keep the conversation on a professional level. Communication II - Have regular meetings with your partners and team members. It's not about checking on them but to get yourself into the right spot to understand their ideas, perspective and concerns regarding the business in general or specific products. Remember, your own ideas always appear brilliant to yourself but get them validated by others and listen to their feedback. Develop the ability to handle criticism in a positive way because if something has been voiced out there's always a reason to do so, and your own experience is obviously limited to yourself, so take advantage from other people's input and use it as an opportunity to learn, to improve, and to provide a better solution than what you're offering right now. Communication III - Proper choice of media. Although, it wasn't explicitly mentioned I'd like to add this based on my own way of communicating with my peers, and on recent conversations I had with a good number of contacts in my professional network. Give yourself a guideline regarding your choice and preferred type of communication. Most of the time it should be in a written manner like email or instant messaging, especially when dealing with clients. But sometimes, there are occasions when it is more appropriate to actually pick up the phone (or any voice-based solution) and have a decent one-to-one conversation. And last but not least keep in touch with your clients irregularly. It's always great to show your appreciation for their business and that you're actually caring about them. And you never know, knocking at someone's door just to say \"Hello\" might give you opportunities and generate new business, too. Lone warrior or team player? Well, on this aspect we had clearly two parties and opinions... Personally, I'd recommend to team up with someone you trust and can rely one, but the arguments regarding souvereignty and decision making processes puts some weight on the other side of the equation. Frankly, I'd say this is a very personal decision as it is hard to find the right people to go into business with, especially while founding a start-up and you need to know whether the relationship will make it or break it during tough times, too. As an entrepreneur you'll have to go extra-miles without any doubts, and you ask yourself whether you have the perseverance to push through it on your own or whether you prefer to have a professional business partner at your side who might be able to literally kick your ass to keep things moving forward. Of course, it's not an easy decision but having a look at some of the most successful companies nowadays might give you a hint (or not) \uD83D\uDE09 - Talking about Apple, Google, Facebook, Tesla Motors, LinkedIn, Amazon, Microsoft, etc. Get experts on board (from the very beginning) Despite your initial decision whether you're going to start your business on your own or to team up with another co-founder you should always take care to get a professional from the accounting department and another expert in the legal department into your company. There are a variety of ways on how to achieve that. And quite frankly it doesn't matter how to get them on board but don't proceed without those two essential knowledge assets. In terms of priority you should get an accountant first, legal support might come into business on its own. But more on that below. Freelancing / services or product oriented business? Whether you are about to start freelancing and offering your resource and knowledge to other people or you're going to develop a set of professional services or products doesn't really matter in terms of starting your own business as long as you're about to solve an existing problem of others. During the various stints of our speakers it was mentioned multiple times that a business is more likely to be focused on a specific field of operations. Some were addressed by friend or another business owner whether it would be possible to help to improve their workflows with some software to be written, or to spice up someone's online marketing with a newly designed and developed web site, or by providing a shopping experience here on the island. Clearly, there was always a very specific problem that ignited a business idea and then developed into a solution to that. But you should also take into consideration that your choice of business model has different aspects of revenue. If you're more likely to operate on a freelancing mode you have to take care of acquisition of new clients on a regular base. Meaning you going to spend to good number of hours per day, per week, per month to look out for potential projects and business opportunities. Not only does this reduce your productive time (meaning chargeable to the client) but it might be cumbersome doing it all over again and over a long period of time. Yes, you might start your business based on an initial project or assignment but bear in mind that this assignment is going to be completed and done one day. It's in your responsibility to monitor the market for new projects and keep up a continuous flow of income. If you're more likely to provide a product-oriented solution you might have to face a initiation phase without being able to generate any kind of revenues. Since a couple months (or even years) there is a growing movement towards crowd-founding such product-based solutions. Getting more money into the business - Do's and Don'ts Be truthful, financial aid for your business is always welcoming and it will give you peace of mind. Knowing that there are enough funds on the bank account allows you to focus on your work or product(s), and nurtures your curiousity to delve deeper in some fields of business or technology. Without any source of regular income or proper funding it won't be \"fun\" to start your business but let's not forget that some great businesses where literally build on nothing. Okay, let's see what should take into consideration for your business. During the meeting we came across a variety of potential solutions - partly contradictory to each other and served with different levels of experience - positive as well as negative ones. One argument somehow stood out clearly compared to others: Don't ask your (or any) bank for money. Although the procedures of writing your business plan, summing up the figures, your estimations on expenses and your predictive forecast on the revenues during the first, second and follow-ups isn't a bad exercise after it remains most commonly an exercise while dealing with the board committees at the bank. Don't get me wrong on this here, yes write a business plan but don't take it to a bank. They literally won't be able to grasp your business idea(s): \"They simply don't get it.\" Dealing with your bank is inevitable and you clearly should stay in good terms with your account executive but don't put yourself into the hassle and headache of providing them with the details of your business. Competition is everywhere and unfortunately there have been cases of \"flattery\" here on the island. What else has been mentioned? Well, some reported of good experience with the local Mauritius Business Growth Scheme (MBGS), about the positive effects of inviting Angel Investors into your business concepts and last but least the collaboration with Venture Capital providers. As for Mauritius, the last two options might be a little bit tricky at the moment but I'm aware that there is a growing interest of foreign investors, especially coming from the U.S.A., in jump-starting or assisting young entrepreneurs. And the other side there are also options to improve your management of expenses as the Mauritian government has public bodies and institutions in place which can clearly be useful in order to grow your business. One opportunity is related to the Youth Employment Programme (YEP) which has a database of potential employees for your business. Another way is to get in touch with the Ministry of Finance and Economic Development in Port Louis. So, despite common understanding that there is hardly any support by the local government our meetup was an eye-opener for a good number of attendees, including myself. For sure, I'll register my business with YEP, and I'm going to propose one or two open positions before the end of the year. The importance of a proper cash flow ALWAYS take care of your income, payment, royalties or whatever source of money for your business first. Your company doesn't operate on love and good promises from your clients but hard cash only. Send out a signal to your clients that you're serious about business and you're a professional company owner. Don't get into any compromises regarding payment from your clients. If it's about new work ask for a certain percentage of down-payment upfront (50% of the total seems to be a common and agreeable condition). Be clear about the payment conditions on your invoices and chase your client immediately when you have suspicions regarding their liquidity. Remember, you have monthly obligations to fulfill and your business relies on a proper cash-flow and in-time payments from your clients. Some of the speakers could report that businesses went into bankruptcy only due to outstanding payments by their clients. Don't let this happen to you, and get your accounting and legal department cracking on the nuts and bolts of your contracts as well as the proper handling of your reminder system. Also, if possible (depending on the country) see whether you can run a background on your leads prior to get into business with them. Here in Mauritius companies have to file their annual business reports publicly to the registrar of companies. Once again, leave this kind of work to your professionals in the accounting department. Hire slow and the right people As mentioned above regarding communication it is very essential and vital for your young business to work with the right people on the team. Whether it is with contracted experts like an accountant or a lawyer it is also important to put a high emphasis on your hiring process of UI designers, software developers, or product workers. Having people on the team which are not in the spirit of working in an agile environment of a start-up, or entertaining a so-called \"social welfare case\" doesn't help to prosper your business. Aux contraire, it's going to slow you down immensely. Pay attention and put some energy into your hiring process, it won't hurt you at all but provide you the right talent for your company. There are great examples available on the world-wide web for free but I'd like you to read the following blog article \"How to hire a lot of talented people, very quickly\" of Ryan Carson, CEO of Treehouse, explicitly. It was inspiring for me and I set up a similar process using Trello, too. Yes, you should hire more people but in a steady way, always in regard to your business revenues, and only stock up your staff with people you're sure they can live up to aims of your business. Networking - the offline way Honestly, I can't remember how often we talk about the importance of growing and entertaining a professional network - both online using platforms like LinkedInor Xingas well as offline by attending any kind of organised events related to business owners. While running a business you have to get the word out that you are actually operational, and of course you have to let people know about the kind of professional services or products you are offering. This is related to self-marketing and you should practice so-called \"elevator pitches\" prior to going out. Ask yourself: Would you be able to describe and summarize your business in less than 5 seconds? Are you able to your profile into one paragraph of 2 to 3 sentences? What are the outstanding benefits your business could offer compared to your competition? If you cannot answer those questions, you should reflect on them and get in better shape for your next get-together with other business people. Some visual impressions Pictures are courtesy of Pritiv and Kevin. MSCC: Sharing my experience on founding and running a start-up. There have been ups and downs throughout the years. MSCC: Crowded audience - we had 35 attendees during our sessions on Entrepreneurship and start-up culture in Mauritius. MSCC: Paying close attention to other speakers' experience as entrepreneurs - at least I got quite some new ideas during those couple of hours. Upcoming Events and networking With such high quality of information and lots of bombshell to talk about and exchange we clearly exceeded our time frame but I'd say it was absolutely worth it. Furthermore, I had the impression that we should have a similar get-together in the near future to continue our intensive conversations. What are the upcoming events here in Mauritius? So far, we have the following ones (incomplete list as usual) in chronological order: Ubuntu Global Jam - Mauritius (12th of September 2014) 36th International Conference of Data Protection and Privacy Commissioners (13 - 16th of October 2014) Developers Conference (TBA ~ January 2015) Hopefully, there will be more announcements during the next couple of weeks and months. If you know about any other event, like a bootcamp, a code challenge or hackathon here in Mauritius, please drop me a note in the comment section below this article. Thanks! My resume of the day Get some real world experience first, build a network of professional contacts, and then think about doing your own business! Seriously, even though the idea of starting a business right after graduation might be tempting, take into consideration that there's a lot to learn and it's definitely not the \"big bucks\" you should be after. Building your own company takes a lot of energy and investment in terms of time and money. Better to be prepared (at least a bit) than to run into some dumb mistakes thousands of others have already been through. Be smart, learn from others' experience, team up in start-up if you prefer a more flexible life-style. Despite the level of self-marketing of some of our speakers there was huge amount of information regarding tips and advice for future business owners. And there were a good number of hiring opportunities, too. Almost everyone was mentioning that they are having open positions. I guess that's one of the cool aspects during our meetups, isn't it? And.... special thanks to Santosh Achari to provide his laptop for the various presentations. Reminder to myself: Bring the DisplayPort-VGA and HDMI-VGA adapters next time... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-20140830-entrepreneurship-and-startup-culture.html'; this.page.identifier = '9271ed1c97_idmscc-20140830-entrepreneurship-and-startup-culture'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-20141025-shellshock-survival-guide.html": {
    "href": "mscc-20141025-shellshock-survival-guide.html",
    "title": "MSCC: Shellshock Survival Guide - Get Blogged by JoKi",
    "summary": "MSCC: Shellshock Survival Guide Copy Markdown View Markdown GitHub Markdown Source MSCC: Shellshock Survival Guide Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-20141025-shellshock-survival-guide'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-20141025-shellshock-survival-guide'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Shellshock Survival Guide') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-10-27 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The media coverage related to vulnerabilities in Linux has been quite immense lately. After Heartbleed during the early months of 2014, we had a second major wave of problems based on a very old \"feature\" in the commonly used bash - Bourne Again Shell - on Linux- and BSD-based systems including Mac OS X. Well, there has been quite some activities and controversial discussion around this feature but it was obvious that it could be exploited and therefore a fix had to be done. Taking into consideration that there are literally millions of systems connected on the internet which are based on a Linux or BSD system, this obviously isn't a quick and easy task to improve. This month's meetup was organised in a joint-venture between the MSCC, the LUGM, the UoM CC and we settled down at the University of Mauritius. Thanks to the organisers and it was again a great experience to be on the campus of Mauritius. Shellshock: Survival Guide Theevent was originally created on Facebook, and at the MSCC we simply picked it in order to attract more people for the meeting. Well, despite the hundreds of \"Event Go'ers\" on Facebook we were roughly 24 people that came together. The provided room 1.14 was big enough for everyone, and eventually we might be able to use this space on a regular base.. to be confirmed. \uD83D\uDE09 My point of view Well... it's best to simply voice it out: \"Despite the technical background of Shellshock there was simply too much distraction and too many discussions going on during the meeting. I found it kind of chaotic and non-informative... Somehow I expected a bit more regarding immediate corrections, advice on how to write better scripts and eventually something related to hardening an OS regarding bash, scripting languages and user-space applications on various Linux distributions, and Mac OS X.\" Quite frankly, I was kind of disappointed by the lack of practical guidance. I mean... \"survival guide\" would implicate that you'll learn something to take home or back to the office, and to apply to your web server or office systems, or that you could integrate in your coding efforts in order to improve your skills and to reduce the risk of a system exploitation, don't you agree? Actually, I thought about my statement for some time, but it didn't come out better than this. Yes, I learned about the implications why shellshock is dangerous and that there are patched versions of all major distributions available but apart from that.... I didn't learn anything new in order to be better aware of such situation or to avoid it completely. MSCC meetup: Discussion about the bash shellshock vulnerability and practical advice to secure your systems. Reactions of other attendees Some other bloggers already put their thoughts online... Nitin - ShellShock - Le guide de survivre Ish - Shellshock: A survival guide Both very informative regarding the events as they happened but same like own observation there's clearly a lack of guidance after all. Upcoming Events and networking We are closing in on year's end and the advertisement for End of Year party venues is increasing. Well' at the MSCC we are already planning our second Christmas activities, too. What are the upcoming events here in Mauritius? So far, we have the following ones (incomplete list as usual) in chronological order: The Hour of Code (8th - 14th December - Global event) Hopefully, there will be more announcements during the next couple of weeks and months. If you know about any other event, like a bootcamp, a code challenge or hackathon here in Mauritius, please drop me a note in the comment section below this article. Thanks! My resume of the day Discussed, dusted and off to new discoveries! This month's event was interesting and although there was no actual \"survival guide\" it is good to see that the awareness in Mauritian IT is growing, especially among students. Nowadays, you can't effort to put on blinders and pretend that your operating system is all safe and secure. It's your continuous responsibility to follow security advisory bulletins and to improve your skills in IT - and it doesn't matter whether you're a system administrator, a software developer, or a passionate web developer. With the increasing amount of Internet of Things (IoT) security, safety and privacy is an ongoing process. Don't just kick back and relax, the next big bang is lurking around the corner - for sure... \uD83D\uDE09 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-20141025-shellshock-survival-guide.html'; this.page.identifier = '9271ed1c97_idmscc-20141025-shellshock-survival-guide'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-21092013-clean-code-developer-certification.html": {
    "href": "mscc-21092013-clean-code-developer-certification.html",
    "title": "MSCC: Clean Code Developer & Certification in IT - Get Blogged by JoKi",
    "summary": "MSCC: Clean Code Developer & Certification in IT Copy Markdown View Markdown GitHub Markdown Source MSCC: Clean Code Developer & Certification in IT Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-21092013-clean-code-developer-certification'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-21092013-clean-code-developer-certification'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Clean Code Developer & Certification in IT') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-09-24 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It was a very busy weekend this time, and quite some hectic to organise the second meetup on a Saturday for the Mauritius Software Craftsmanship Community (MSCC) but it was absolutely fun. Following, I'm writing a brief summary about the topics we spoke about and the new impulses I got. \"What a meetup... I was positively impressed. At the beginning I thought that noone would actually show up but then by the time the room got filled. Lots of conversation, great dialogues and fantastic networking between fresh students, experienced students, experienced employees, and self-employed attendees. That's what community is all about!\" Above quote was my first reaction shortly after the gathering. And despite being busy during the weekend and yesterday, I took my time to reflect a little bit on things happened and statements made before writing it here on my blog. Additionally, I was also very curious about possible reactions and blogs from other attendees. Reactions from other craftsmen Let me quickly give you some links and quotes from others first... \"Like Jochen posted on facebook, that was indeed a 5+ hours marathon (maybe 4 hours for me but still) … Wohoo! We’re indeed a bunch of crazy geeks who did not realise how time flew as we dived into the myriad discussions that sprouted. Yet in the end everyone was happy (:\" -- Ish on MSCC meetup - The marathon (: \"And the 4hours spent @ Talking drums bore its fruit..I was doing something I never did before....reading the borrowed book while walking....and though I was not that familiar with things mentionned in the book...I was skimming,scanning & flipping...reading titles...short paragraphs...and I skipped pages till I reached home.\" -- Yannick on Mauritius Software Craftsmanship 1st Meet-up \"Hi Developers, Just wanted to share with you the meetups i attended last Saturday - [...] - The second meetup is the one hosted by Jochen Kirstätter, the MSCC, where the attendees were Craftsman, no woman, this time - all sharing the same passion of being a developer - even though it is on different platforms(Windows - Windows Phone - Linux - Adobe(yes a designer) - .Net) - but we manage to sit at the same table - sharing developer views and experience in the corporate world - also talking about good practice when coding( where Jochen initiated a discussion on Clean Coding ) i could not stay till the end - but from what i have heard - the longer you stay the more fun you have till 1600. Developers in the Facebook grouping i invite you to stay tuned about the various developer communities popping up - where you can come to share and learn good practices, develop the entrepreneurial spirit, and learn and share your passion about technologies\" -- Arnaud on Facebook More feedback has been posted on the event directly. So, should I really write more? Wouldn't that spoil the impressions? Starting the day with a surprise Indeed, I was very pleased to stumble over the existence of Mobile Monday Mauritius on LinkedIn, an association about any kind of mobile app development, mobile gadgets and latest smartphones on the market. Despite the Monday in their name they had scheduled their recent meeting on Saturday between 10:00 and 12:00hrs. Wow, what a coincidence! Let's grap the bull by its horns and pay them an introductory visit. As they chose the Ebene Accelerator at the Orange Tower in Ebene it was a no-brainer to leave home a bit earlier and stop by. It was quite an experience and fun to talk to the geeks over there. Really looking forward to organise something together.... Arriving at the venue As the children got a bit uneasy at the MoMo gathering and I didn't want to disturb them too much, we arrived early at Bagatelle. Well, no problems as we went for a decent breakfast at Food Lover's Market. Shortly afterwards we went to our venue location, Talking Drums, and prepared the room for the meeting. We only had to take off a repro-painting of the wall in order to have a decent area for the projector. All went very smooth and my two little ones were of great help. Just in time, our first craftsman Avinash arrived on the spot. And then the waiting started... Luckily, not too long. Bit by bit more and more IT people came to join our meeting. Meanwhile, I used the time to give a brief introduction about the MSCC in general, what we are (hm, maybe I am) trying to achieve and that the recent phase is completely focused on creating more awareness that a community like the MSCC is active here in Mauritius. As soon as we reached some 'critical mass' of about ten people I asked everyone for a short introduction and bio, just in case... Conversation between participants started to kick in and we were actually more networking than having a focus on our topics of the day. * Quick updates on latest news and development around the MSCC* Finally, Clean Code Developer No matter how the position is actually called, whether it is Software Engineer, Software Developer, Programmer, Architect, or Craftsman, anyone working in IT is facing almost the same obstacles. As for the process of writing software applications there are re-occurring patterns and principles combined with some common exercise and best practices on how to resolve them. Initiated by the must-read book 'Clean Code' by Robert C. Martin (aka Uncle Bob) the concept of the Clean Code Developer (CCD) was born already some years ago. CCD is much likely to traditional martial arts where you create awareness of certain principles and learn how to apply practices to improve your style. The CCD initiative recommends to indicate your level of knowledge and experience with coloured wrist bands - equivalent to the belt colours - for various reasons. Frankly speaking, I think that the biggest advantage here is provided by the obvious recognition of conceptual understanding. For example, take the situation of a team meeting... A member with a higher grade in CCD, say Green grade, sees that there are mainly Red grades to talk to, and adjusts her way of communication to their level of understanding. The choice of words might change as certain elements of CCD are not yet familiar to all team members. So instead of talking in an abstract way which only Green grades could follow the whole scenario comes down to Red grade level. Different story, better results... Similar to learning martial arts, we only covered two grades during this occasion - black and red. Most interestingly, there was quite some positive feedback and lots of questions about the principles and practices of the red grade. And we gathered real-world examples from various craftsman and discussed them. Following the Clean Code Developer Red Grade and some annotations from our meetup: CCD Red Grade - Principles Don't Repeat Yourself - DRY Keep It Simple, Stupid (and Short) - KISS Beware of Optimisations! Favour Composition over Inheritance - FCoI Interestingly most of the attendees already heard about those key words but couldn't really classify or categorize them. It's very similar to a situation in which you do not the particular for a thing and have to describe it to others... until someone tells you the actual name and suddenly all is very simple. CCD Red Grade - Practices Follow the Boy Scouts Rule Root Cause Analysis - RCA Use a Version Control System Apply Simple Refactoring Pattern Reflect Daily * Introduction to the principles and practices of Clean Code Developer - here: Red Grade* As for the various ToDo's we commonly agreed that the Boy Scout Rule clearly is not limited to software development or IT administration but applies to daily life in general. Same for the root cause analysis, btw. We really had good stories with surprisingly endings and conclusions. A quick check about who is using a version control system brought more drive into the conversation. Not only that we had people that aren't using any VCS at all, we also had the 'classic' approach of backup folders and naming conventions as well as the VCS 'junkie' that has to use multiple systems at a time. Just for the records: Git and GitHub seem to be in favour of some of the attendees. Regarding the daily reflection at the end of the day we came up with an easy solution: Wrap it up as a blog entry! Certifications in IT This is kind of a controversy in IT in general. Is it interesting to go for certifications or are they completely obsolete? What are the possibilities to get certified? What are the options we have in Mauritius? How would certificates stand compared to other educational tracks like Computer Science or Web Design. The ratio between craftsmen with certifications like MCP, MSTS, CCNA or LPI versus the ones without wasn't in favour for the first group but there was a high interest in the topic itself and some were really surprised to hear that exam preparations are completely free available online including temporarily voucher codes for either discounts or completely free exams. Furthermore, we discussed possible options on forming so-called study groups on a specific certificates and organising more frequent meetups in order to learn together. Taking into consideration that we have sponsored access to the video course material of Pluralsight (and now PeepCode as well as TrainSignal), we might give it a try by the end of the year. Current favourites are LPIC Level 1 and one of the Microsoft exams 40-78x. Feedback and ideas for the MSCC The closing conversations and discussions about how the MSCC is recently doing, what are the possibilities and what's (hopefully) going to happen in the future were really fertile and I made a couple of mental bullet points which I'm looking forward to tackle down together with orher craftsmen. Eventually, it might be a good option to elaborate on some issues during our weekly Code & Coffee sessions one Wednesday morning. * Active discussion on various IT topics like certifications (LPI, MCP, CCNA, etc) and sharing experience * Finally, we made it till the end of the planned time. Well, actually the talk was still on and we continued even after 16:00hrs. Unfortunately, we (the children and I) had to leave for evening activities. My resume of the day... It was great to have 15 craftsmen in one room. There are hundreds of IT geeks out there in Mauritius, and as Mauritius Software Craftsmanship Community we still have a lot of work to do to pass on the message to some more key players and companies. Currently, it seems that we are able to attract a good number of students in Computer Science... but we have a lot more to offer, even or especially for IT people on the job. I'm already looking forward to our next Saturday meetup in the near future. PS: Meetup pictures are courtesy of Nirvan Pagooah. Thanks for sharing... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-21092013-clean-code-developer-certification.html'; this.page.identifier = '9271ed1c97_idmscc-21092013-clean-code-developer-certification'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-25012014-html5-css3-javascript.html": {
    "href": "mscc-25012014-html5-css3-javascript.html",
    "title": "MSCC: HTML5, CSS3 and JavaScript - Get Blogged by JoKi",
    "summary": "MSCC: HTML5, CSS3 and JavaScript Copy Markdown View Markdown GitHub Markdown Source MSCC: HTML5, CSS3 and JavaScript Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-25012014-html5-css3-javascript'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-25012014-html5-css3-javascript'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: HTML5, CSS3 and JavaScript') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-01-25 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Today's meetup has been an interesting experience for me... for two reasons actually. First, I have to be more active with our craftsmen, especially with our speakers of the day. And second, we definitely need more space. Even both conference rooms together at the Ebene Accelerator are not enough anymore for our monthly meetings. We set a new record: 27 attendees! Following my impression on today's event: \"Good to see new craftsmen and regular attendees during our meetups. Especially on Saturdays, we are getting more people together and it is improving from month to month. Despite having some initial start problems today, it was a good get-together and with our three speakers we had quite a good opportunity to learn things. Surely, there is still room for improvement. We are working hard on that... I was very pleased to have our guest speaker today! Even though I was a bit occupied with other networking related topics for our future meetings.\" I'm really really glad and grateful about the growing interest in our community. Thanks guys, you rock! Prediction of harsh weather conditions Harsh weather conditions have been predicted by Facebook for our monthly meetup... even probability of snow! Reactions from other craftsmen None yet... to be updated soon \uD83D\uDE09 Finally, a little slot of spare time to gather some impressions from other attendees... \"I'd rather prefer having two versions of a website, one adapted for desktop screen and the other for mobile. When creating responsive pages, I believe quite some space is lost.\" -- Ish on MSCC, a growing army \"Ensuite, Andrew a fait une brève introduction sur lui-même et les perspectives sur le marché mauricien, les perspectives au niveau international et le niveau des salaires.\" -- Nitin on L'équipe #MSCC #LUGM s'unissent à Ebene Accelerator In general, I have the impression that we are on the right way. Our future looks bright! ICT scene from the view of an HR person Thankfully, Andrew Ragaven gave me a helping hand and not only introduced himself as a Human Resources (HR) and Administration Manager or Talent Acquisition Specialist - depending on how you'd like to see it. Anyway, he had some great pieces of information about the local ICT scene in comparison to the rest of world. According to his estimation there are currently 15,000 people in ICT in Mauritius, and there is still more demand for resources in general but also - IMHO more alarming - a demand for better qualified people. During and after his brief introduction a couple of questions directly came up and it was really nice to see that he might have hit something here. Dhiruj gave great advise to candidates based on his work experience back in the UK, and I could also add my experience with candidates to this. But... the essential information hereby remains that it seems that the local work force is provided under value compared to other nations. Very interesting indeed, given the aspect that we already spoke about freelancing and hourly rates in past meetups. Preprocessors and some nifty tools for writing HTML Although, this topic was planned as a follow-up to the introduction to HTML5, our both guest speakers swapped their slots and Sun started today's technical part of day. As already stated, I wasn't around during the session - active networking with someone at the Ebene Accelerator which is responsible for regional events and activity organisation in the Indian Ocean. I'll catch up on that after the scheduled meeting with her. Anyway, I'm looking forward to get the presentation from Sun, and I'm going to publish it here, too. And here we go... The slides are hosted on Prezi: Tools for better web development: Responsive Web Development (RWD) Did I mention that the begin of this meeting was kind of tough for me? Oh yes \uD83D\uDE09 Well, Mozammil came a bit later than expected but I grateful that he didn't cancel in the last minute. I'm going to provide the presentation ASAP. Meanwhile, please check out Mozammil's blog. It has some great information on performance improvements of web sites. Interesting side note from Nadim: \"And dear students (and perhaps professionals). Remember www.w3fools.com and forget w3schools.com\" Events What are the upcoming events here in Mauritius? So far, we have the following ones (incomplete list as usual) in chronological order: Corsair Hackers Reboot (8.2.2014) Code Challenge (TBA ~ Feb 2014) Innovation Day(s) (4.4.2014) WebCup (TBA ~ June 2014) Developers Conference (TBA ~ July 2014) Linuxfest 2014 (TBA ~ November 2014) Hopefully, there will be more announcements during the next couple of weeks and months. One of the hottest candidates that I would like to have here in Mauritius would be Microsoft BootCamp and Windows 8.1 and/or Windows Phone 8 development. Let's what Microsoft is going to pull off this year. Hm, which reminds me that there will be the launch event of SQL Server 2014... Networking and job/project opportunities Due to our first timers there was again some good possibilities to exchange a couple of contact information and once again we came across the important aspect to promote yourself online. Setup a personal website, use it as your online CV / resume, and write a technical article about your work or private IT activities from time to time. It was really pleasant to have Louis around today. Some month back I got a request from his friend Vincent that he is actually looking for a job as Web Developer in Mauritius. Having no open position at IOS Indian Ocean Software Ltd. I passed his contacts via the #MSCC social media channels. Also, please don't forget to get in touch with the Ebene Accelerator for their demand on web development. Following some pictures from today's event: MSCC meetup of 25.01.2014: Craftsmen listing to the presentation on Responsive Web Development (RWD) MSCC meetup of 25.01.2014: More craftsmen occupying the conference rooms of Ebene Accelerator MSCC meetup of 25.01.2014: Our usual outfit during our gatherings... laptops, smartphones and books everywhere MSCC meetup of 25.01.2014: We need more space! Today, there were 27 (+2) attendees... My resume of the day We need more space! Frankly speaking, it is now more than obvious that we are exceeding the capacity of the Ebene Accelerator. Which is very good on the one hand and hopefully will give us more room in future meetings but also a bit sad as we received great support from the beginning. Some of our craftsmen even applied at the accelerator with newly created startup companies. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-25012014-html5-css3-javascript.html'; this.page.identifier = '9271ed1c97_idmscc-25012014-html5-css3-javascript'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-26102013-clean-code-developer-flexible-work-environment.html": {
    "href": "mscc-26102013-clean-code-developer-flexible-work-environment.html",
    "title": "MSCC: Clean Code Development & Flexible work environment - Get Blogged by JoKi",
    "summary": "MSCC: Clean Code Development & Flexible work environment Copy Markdown View Markdown GitHub Markdown Source MSCC: Clean Code Development & Flexible work environment Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-26102013-clean-code-developer-flexible-work-environment'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-26102013-clean-code-developer-flexible-work-environment'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Clean Code Development & Flexible work environment') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-10-28 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Finally, some spare time to summarize my impressions and experiences of the recent meetup of Mauritius Software Craftsmanship Community. I already posted my comment on the event and on our social media networks: Professional - It's getting better with our meetups and I really appreciated that 'seniors' and 'juniors' were present today. Despite running a little bit out of time it was really great to see more students coming to the gathering. This time we changed location for our Saturday meetup and it worked out very well. A big thank you to Ebene Accelerator, namely Mrs Poonum, for the ability to use their meeting rooms for our community get-together. Already some weeks ago I had a very pleasant conversation with her about the MSCC aims, 'mission' and how we organise things. Additionally, I think that an environment like the Ebene Accelerator is a good choice as it acts as an incubator for young developers and start-ups. Reactions from other craftsmen Before I put my thoughts about our recent meeting down, I'd like to mention and cross-link to some of the other craftsmen that were present: \" MSCC meet up is a massive knowledge gaining strategies for students, future entrepreneurs, or for geeks all around. Knowledge sharing becomes a fun. For those who have not been able to made it do subscribe on our MSCC meet up group at meetup.com.\" -- Nitin on Learning is fun with #MSCC #Ebene Accelerator \"We then talked about the IT industry in Mauritius, salary issues in various field like system administration, software development etc. We analysed the reasons why people tend to hop from one company to another. That was a fun debate.\" -- Ish on MSCC meetup - Gang of Geeks \"Flexible Learning Environment was quite interesting since these lines struck cords : \"You're not a secretary....9 to 5 shouldn't suit you\"....This allowed reflection...deep reflection....especially regarding the local mindset...which should be changed in a way which would promote creativity rather than choking it till death...\" -- Yannick on 2nd MSCC Monthly Meet-up And others on Facebook... \uD83D\uDE09 Visual impressions are available on our Meetup event page. More first time attendees We great pleasure I noticed that we have once again more first time visitors. A quick overlook showed that we had a majority of UoM students in first, second or last year. Some of them are already participating in the UoM Computer Club or are nominated as members of the Microsoft Student Partner (MSP) programme. Personally, I really appreciate the fact that the MSCC is able to gather such a broad audience. And as I wrote initially, the MSCC is technology-agnostic; we want IT people from any segment of this business. Of course, students which are about to delve into the 'real world' of working are highly welcome, and I hope that they might get one or other glimpse of experience or advice from employees. Sticking to the schedule? No, not really... And honestly, it was a good choice to go a little bit of the beaten tracks. I mean, yes we have a 'rough' agenda of topics that we would like to talk about or having a presentation about. But we keep it 'agile'. Due to the high number of new faces, we initiated another quick round of introductions and I gave a really brief overview of the MSCC. Next, we started to reflect on the Clean Code Developer (CCD) - Red Grade which we introduced on the last meetup. Nirvan was the lucky one and he did a good job on summarizing the various abbreviations of the first level of being a CCD. Actually, more interesting, we exchanged experience about the principles and practices of Red Grade, and it was very informative to get to know that Yann actually 'interviewed' a couple of friends, other students, local guys working in IT companies as well as some IT friends from India in order to counter-check on what he learned first-hand about Clean Code. Currently, he is reading the book of Robert C. Martin on that topic and I'm looking forward to his review soon. More output generates more input What seems to be like a personal mantra is working out pretty well for me since the beginning of this year. Being more active on social media networks, writing more article on my blog, starting the Mauritius Software Craftsmanship Community, and contributing more to other online communities has helped me to receive more project requests, job offers and possibilities to expand my business at IOS Indian Ocean Software Ltd. Actually, it is not a coincidence that one of the questions new craftsmen should answer during registration asks about having a personal blog. Whether you are just curious about IT, right in the middle of your Computer Studies, or already working in software development or system administration since a while you should consider to advertise and market yourself online. Easiest way to resolve this are to have online profiles on professional social media networks like LinkedIn, Xing, Twitter, and Google+ (no Facebook should be considered for private only), and considering to have a personal blog. Why? -- Be yourself, be proud of your work, and let other people know that you're passionate about your profession. Trust me, this is going to open up opportunities you might not have dreamt about... Exchanging ideas about having a professional online presence - MSCC meetup on the 26th October 2013 Furthermore, consider to put your Curriculum Vitae online, too. There are quite a number of service providers like 1ClickCV, Stack Overflow Careers 2.0, etc. which give you the ability to have an up to date CV online. At least put it on your site, next to your personal blog. Similar to what you would be able to see on my site here. Cyber Island Mauritius - are we there? A couple of weeks ago I got a 'cold' message on LinkedIn from someone living in the U.S. asking about the circumstances and conditions of the IT world of Mauritius. He has a great business idea, venture capital and is currently looking for a team of software developers (mainly mobile - iOS) for a new startup here in Mauritius. Since then we exchanged quite some details through private messages and Skype conversations, and I suggested that it might be a good chance to join our meetup through a conference call and see for yourself about potential candidates. During approximately 30 to 40 minutes the brief idea of the new startup was presented - very promising state-of-the-art technology aspects and integration of various public APIs -, and we had a good Q&A session about it. Also thanks to the excellent bandwidth provided by the Ebene Accelerator the video conference between three parties went absolutely well. Clean Code Developer - Orange Grade Hahaha - nice one... Being at the Orange Tower at Ebene and then talking about an Orange Grade as CCD. Well, once again I provided an overview of the principles and practices in that rank of Clean Code, and similar to our last meetup we discussed on the various aspect of each principle, whether someone already got in touch with it during studies or work, and how it could affect their future view on their source code. Following are the principles and practices of Clean Code Developer - Orange Grade: CCD Orange Grade - Principles Single Level of Abstraction (SLA) Single Responsibility Principle (SRP) Separation of Concerns (SoC) Source Code conventions CCD Orange Grade - Practices Issue Tracking Automated Integration Tests Reading, Reading, Reading Reviews Especially the part on reading technical books got some extra attention. We quickly gathered our views on that and came up with a result that ranges between Zero (0) and up to Fifteen (15) book titles per year. Personally, I'm keeping my progress between Six (6) and Eight (8) titles per year, but at least One (1) per quarter of a year. Which is also connected to the fact that I'm participating in the O'Reilly Reader Review Program and have a another benefit to get access to free books only by writing and publishing a review afterwards. We also had a good exchange on the extended topic of 'Reviews' - which to my opinion is abnormal difficult here in Mauritius for various reasons. As far as I can tell from my experience working with Mauritian software developers, either as colleagues, employees or during consulting services there are unfortunately two dominant pattern on that topic: Keeping quiet Running away Honestly, I have no evidence about why these are the two 'solutions' on reviews but that's the situation that I had to face over the last couple of years. Sitting together and talking about problematic issues, tackling down root causes of de-motivational activities and working on general improvements doesn't seem to have a ground within the IT world of Mauritius. Are you a typist or a creative software craftsman? - MSCC meetup on the 26th October 2013 One very good example that we talked about was the fact of 'job hoppers' as you can easily observe it on someone's CV - those people change job every single year; for no obvious reason! Frankly speaking, I wouldn't even consider an IT person like to for an interview. As a company you're investing money and effort into the abilities of your employees. Hiring someone that won't stay for a longer period is out of question. And sorry to say, these kind of IT guys smell fishy about their capabilities and more likely to cause problems than actually produce productive results. One of the reasons why there is a probation period on an employment contract is to give you the liberty to leave as early as possible in case that you don't like your new position. Don't fool yourself or waste other people's time and money by hanging around a full year only to snatch off the bonus payment... Future outlook: Developer's Conference Even though it is not official yet I already mentioned it several times during our weekly Code & Coffee sessions. The MSCC is looking forward to be able to organise or to contribute to an upcoming IT event. Currently, the rough schedule is set for April 2014 but this mainly depends on availability of location(s), a decent time frame for preparations, and the underlying procedures with public bodies to have it approved and so on. As soon as the information about date and location has been fixed there will be a 'Call for Papers' period in order to attract local IT enthusiasts to apply for a session slot and talk about their field of work and their passion in IT. More to come for sure... My resume of the day It was a great gathering and I am very pleased about the fact that we had another 15 craftsmen (plus 2 businessmen on conference call plus 2 young apprentices) in the same room, talking about IT related topics and sharing their experience as employees and students. Personally, I really appreciated the feedback from the students about their current view on their future career, and I really hope that some of them are going to pursue their dreams. Start promoting yourself and it will happen... Looking forward to your blogs! And last but not least our numbers on Meetup and Facebook have been increased as a direct consequence of this meetup. Please, spread the word about the MSCC and get your friends and colleagues to join our official site. The higher the number of craftsmen we have the better chances we have t achieve something great! Thanks! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-26102013-clean-code-developer-flexible-work-environment.html'; this.page.identifier = '9271ed1c97_idmscc-26102013-clean-code-developer-flexible-work-environment'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-career-it-fair-2014.html": {
    "href": "mscc-career-it-fair-2014.html",
    "title": "MSCC: Career & IT Fair 2014 - Get Blogged by JoKi",
    "summary": "MSCC: Career & IT Fair 2014 Copy Markdown View Markdown GitHub Markdown Source MSCC: Career & IT Fair 2014 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-career-it-fair-2014'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-career-it-fair-2014'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Career & IT Fair 2014') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-04-26 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Already a couple of weeks ago, I've been addressed by Ibraahim and Yunus to see whether it would be interesting to participate in the 1st Career & IT Fair organised by the UoM Computer Club. Well, luckily we met at the Global Windows Azure Bootcamp and I wasn't too sure whether it would be possible for me to attend after all. The main reason is given because of work demand and furthermore due to the fact that the Mauritius Software Craftsmanship Community currently has no advertising material at all. Here's the brief statement of the event: \"The UOM Students' Computer Club in collaboration with the UOM Students' Union and UOM CSE Department is organising a 'Career & IT Fair' on the 23rd and 24th April 2014. This event has for objective to provide a platform to tertiary students, secondary students as well as vocational students, the opportunity to meet job recruiters.\" Luckily, I was reminded that the 23rd is a Wednesday, and therefore I decided that it might be interesting to move our weekly Code & Coffee session to the university and hence be able to attend the career fair. As it turned out it was a great choice and thankfully Pritvi, Nadim as well as Ishwon volunteered to be around at the \"community booth\". Thankfully, the computer club gave us - the MSCC and the LUGM - one of their spaces in the lobby area of the Paul Octave Wiéhé Auditorium. My impression about the event Very well and professionally organised. Seriously, the lads over at the UoM Computer Club did a great job in organising their 2 days event, and felt very comfortable at any time. Actually, it was kind of amusing to some of the members constantly running around and checking everything. Even though that the whole process went smooth and easy off the hand. There were a couple of interesting pieces of information and announcements during the opening ceremony. For example, the Computer Science faculty is a very young one and has been initiated back in 1988 only - just by 4 staff members at that time. Now, after 25 years they have achieved quite a lot and there are currently 1.000+ active students attending the numerous lectures and courses. But there is no room to rest on previous achievements, and I was kind of surprised to hear that there are plans to extend the campus, and offer new lectures in the fields of nanotechnology, big data handling, and - crossing fingers - the introduction and establishment of a space control centre. Mauritius is already part of the Square Kilometre Array (SKA) and hopefully there will be more activities into that direction in the near future. Community - Awareness and collaboration As stated earlier, I could only spent one morning but luckily other members of the MSCC and the LUGM stayed during the whole two days and provided answers to any interested person. As for me, I took the opportunity to get in touch with the other companies in the lobby. Mainly, to create some awareness about our IT communities but also to see whether there might be options for future engagement in common activities, too. So far, I was able to speak to representatives of the following companies: ACCA Mauritius Business at Work Infomil LinkByNet Microsoft Indian Ocean Islands & French Pacific Spherinity Training Institute Spoon Consulting Ltd. State Informatics Ltd. Unfortunately, I only had a quick chat with an HR representative of LinkByNet but I fully count on our MSCC members like Nitin or LUGM member Ronny to spread our intentions over there. So far, all of the representatives were really interested in our concepts and activities and I'm currently catching up with an introduction flyer for the MSCC that I'm going to send out to all those contacts via mail. It would be great to have more craftsmen as well as professional support on board. Some pictures from the event MSCC: Fantastic outlook for the near future. Announcements were made on Big data, nanotechnology, and space control centre in Mauritius. Interesting! MSCC: The lobby area was cramped with students. Great way to exchange and network. Good luck to all candidates! Passing the relay staff to... I recommend you to continue to read about the first Career & IT Fair on Ish's blog. He has a great summary and more details on those two days of IT activities than I have. Thanks and feel free to leave a comment (or two)... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-career-it-fair-2014.html'; this.page.identifier = '9271ed1c97_idmscc-career-it-fair-2014'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-global-windows-azure-bootcamp-2014.html": {
    "href": "mscc-global-windows-azure-bootcamp-2014.html",
    "title": "MSCC: Global Windows Azure Bootcamp - 29th March 2014 - Get Blogged by JoKi",
    "summary": "MSCC: Global Windows Azure Bootcamp - 29th March 2014 Copy Markdown View Markdown GitHub Markdown Source MSCC: Global Windows Azure Bootcamp - 29th March 2014 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-global-windows-azure-bootcamp-2014'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-global-windows-azure-bootcamp-2014'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Global Windows Azure Bootcamp - 29th March 2014') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-03-22 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The Mauritius Software Craftsmanship Community proudly presents you the Global Windows Azure Bootcamp 2014 in Mauritius. Global Windows Azure Bootcamp 2014 in Mauritius - MSCC together with Microsoft, Ceridian and Emtel We are very happy and excited about our participation in this global event and would like to draw your attention to the official invitation letter below. Please sign up and RSVP on the official website of the MSCC. Participation is for free! Call for action Please create more awareness of this event in Mauritius and use the hash tag #gwabmru as well as the shortened link: https://aka.ms/gwabmru And remember: Sharing is Caring! Official invitation letter to the GWAB 2014 in Mauritius With over 130 confirmed locations around the globe, the Global Windows Azure Bootcamp is going to be a truly memorable event - and now here's your chance to take part! In April of 2013 we held the first Global Windows Azure Bootcamp at more than 90 locations around the globe! This year we want to again offer up a one day deep dive class to help thousands of people get up to speed on discovering Cloud Computing Applications for Windows Azure. In addition to this great learning opportunity the hands on labs will feature pooling a huge global compute farm to perform diabetes research! In Mauritius, the event will be organised by Microsoft Indian Ocean Islands & French Pacific in partnership with The Mauritius Software Craftsmanship Community (MSCC) and sponsored by Microsoft, Ceridian and Emtel. What do I need to bring? You will need to bring your own computer which can run Visual Studio 2012 or 2013 (i.e. Windows, OSX, Ubuntu with virtualization, etc.) and have it preloaded with the following: Visual Studio 2012 or 2013 The Windows Azure SDK - https://www.windowsazure.com/en-us/develop/net/ Optionally (or if you will not be doing just .NET labs), the following can also be installed: Node.js SDK - https://www.windowsazure.com/en-us/develop/nodejs/ JAVA SDK - https://www.windowsazure.com/en-us/develop/java/ Doing mobile? Android? iOS? Windows Phone or Windows 8? - https://www.windowsazure.com/en-us/develop/mobile/ PHP - https://www.windowsazure.com/en-us/develop/php/ More info here: https://www.windowsazure.com/en-us/documentation Important: Please do the installation upfront as there will be very little time to troubleshoot installations during the day. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-global-windows-azure-bootcamp-2014.html'; this.page.identifier = '9271ed1c97_idmscc-global-windows-azure-bootcamp-2014'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-global-windows-azure-bootcamp.html": {
    "href": "mscc-global-windows-azure-bootcamp.html",
    "title": "MSCC: Global Windows Azure Bootcamp - Get Blogged by JoKi",
    "summary": "MSCC: Global Windows Azure Bootcamp Copy Markdown View Markdown GitHub Markdown Source MSCC: Global Windows Azure Bootcamp Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-global-windows-azure-bootcamp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-global-windows-azure-bootcamp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Global Windows Azure Bootcamp') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-04-02 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Mauritius participated and contributed to the Global Windows Azure Bootcamp 2014 (GWAB). Again! And this time stronger than ever, and together with 137 other locations in 56 countries world-wide. We had 62 named registrations, 7 guest additions and approximately 10 offline participants prior to the event day. Most interestingly the organisation of the GWAB through the MSCC helped to increased the number of craftsmen. The Mauritius Software Craftsmanship Community has currently 138 registered members - in less than one year! Only with those numbers we can proudly say that all the preparations and hard work towards this event already paid off. Personally, I'm really grateful that we had this kind of response and the feedback from some attendees confirmed that the MSCC is on the right track here on Cyber Island Mauritius. Inspired and motivated by the success of this event, rest assured that there will be more public events like the GWAB. This time it took some time to reflect on our meetup, following my first impression right on spot: \"Wow, what an experience to organise and participate in this global event. Overall, I've been very pleased with the preparations and the event itself. Surely, there have been some nicks that we have to address and to improve for future activities like this. Quite frankly, we are not professional event organisers (not yet) but we learned a lot over the past couple of days. A big Thank You to our event sponsors, namely Microsoft Indian Ocean Islands & French Pacific, Ceridian Mauritius and Emtel. Without them this event wouldn't have happened - Thank You! And to the cool team members of Microsoft Student Partners (MSPs). You geeks did a great job! Thanks!\" So, how many attendees did we actually have? 61! - Awesome - 61 cloud computing instances to help on the research of diabetes. During Saturday afternoon there was even an online publication on L'Express: Les développeurs mauriciens se joignent au combat contre le diabète Reactions of other attendees Don't take my word for granted... Here are some impressions and feedback from our participants: \"Awesome event, really appreciated the presentations \uD83D\uDE03\" -- Kevin on event comments \"very interesting and enriching.\" -- Diana on event comments \"#gwab #gwabmru 2014 great success. Looking forward for gwab 2015\" -- Wasiim on Twitter \"Was there till the end. Awesome Event. I'll surely join upcoming meetup sessions \uD83D\uDE03\" -- Luchmun on event comments \"#gwabmru was not that cool. left early\" -- Mohammad on Twitter The overall feedback is positive but we are absolutely aware that there quite a number of problems we had to face. We are already looking into that and ideas / action plans on how we will be able to improve it for future events. The sessions We started the day with welcoming speeches by Thierry Coret, Sr. Marketing Manager of Microsoft Indian Ocean Islands & French Pacific and Vidia Mooneegan, Managing Director and Sr. Vice President of Ceridian Mauritius. The clear emphasis was on the endless possibilities of cloud computing and how it can enable any kind of sectors here in the country. Then it was about time to set up the cloud computing services in order to contribute each attendees cloud computing resources to the global research of diabetes, a step by step guide presented by Arnaud Meslier, Technical Evangelist at Microsoft. Given a rendering package and a configuration file it was very interesting to follow the single steps in Windows Azure. Also, during the day we were not sure whether the set up had been correctly, as Mauritius didn't show up on the results board - which should have been the case after approximately 20 to 30 minutes. Anyways, let the minions work... Next, Arnaud gave a brief overview of the variety of services Windows Azure has to offer. Whether you need a development environment for your websites or mobiles app, running a virtual machine with your existing applications or simply putting a SQL database online. No worries, Windows Azure has the right packages available and the online management portal is really easy t handle. After this, we got a little bit more business oriented while Wasiim Hosenbocus, employee at Ceridian, took the attendees through the inerts of a real-life application, and demoed a couple of the existing features. He did a great job by showing how the different services of Windows Azure can be created and pulled together. After the lunch break it is always tough to keep the audience awake... And it was my turn. I gave a brief overview on operating and managing a SQL database on Windows Azure. Well, there are actually two options available and depending on your individual requirements you should be aware of both. The simpler version is called SQL Database and while provisioning only takes a couple of seconds, you should take into consideration that SQL Database has a number of constraints, like limitations on the actual database size - up to 5 GB as web edition and up to 150 GB maximum as business edition -, among others. Next, it was Chervine Bhiwoo's session on Windows Azure Mobile Services. It was absolutely amazing to see that the mobiles services directly offers you various project templates, like Windows 8 Store App, Android app, iOS app, and even Xamarin cross-platform app development. So, within a couple of minutes you can have your first mobile app active and running on Windows Azure. Furthermore, Chervine showed the attendees that adding another user interface, like Web Sites running on ASP.NET MVC 4 only takes a couple of minutes, too. And last but not least, we rounded up the day with Windows Azure Websites and hosting of Virtual Machines presented by some members of the local Microsoft Students Partners programme. Surely, one of the big advantages using Windows Azure is the availability of pre-defined installation packages of known web applications, like WordPress, Joomla!, or Ghost. Compared to running your own web site with a traditional web hoster it is surely en par, and depending on your personal level of expertise, Windows Azure provides you more liberty in terms of configuration than maybe a shared hosting environment. Running a pre-defined web application is one thing but in case that you would like to have more control over your hosting environment it is highly advised to opt for a virtual machine. Provisioning of an Ubuntu 12.04 LTS system was very simple to do but it takes some more minutes than you might expect. So, please be patient and take your time while Windows Azure gets everything in place for you. Afterwards, you can use a SecureShell (ssh) client like Putty in case of a Linux-based machine, or Remote Desktop Services when operating a Windows Server system to log in into your virtual machine. At the end of the day we had a great Q&A session and we finalised the event with our raffle of goodies. Participation in the raffle was bound to submission of the session survey and most gratefully we had a give-away for everyone. What a nice coincidence to finish of the day. Note: All session slides (and demo codes) will be made available on the MSCC event page. Please, check the Files section over there. (Some) Visual impressions from the event Just to give you an idea about what has happened during the GWAB 2014 at Ebene... Speakers and Microsoft Student Partners are getting ready for the Global Windows Azure Bootcamp 2014 GWAB 2014 attendees are fully integrated into the hands-on-labs and setting up their individuals cloud computing services 60 attendees at the GWAB 2014. Despite some technical difficulties we had a great time running the event GWAB 2014: Using the lunch break for networking and exchange of ideas - Great conversations and topics amongst attendees There are more pictures on the original event page: Questions & Answers Following are a couple of questions which have been asked and didn't get an answer during the event: Q: Is it possible to upload static pages via FTP? A: Yes, you can. Have a look at the right side column on the dashboard of your website. There you'll find information about the FTP and SFTP host names. You can use any FTP client, like ie. FileZilla to log in. FTP also gives you access to your log files. Q: What are the size limitations on SQL Database? A: 5 GB on the Web Edition, and 150 GB on the business edition. A maximum 150 databases (inclusing 'master') per SQL Database server. More details here: General Guidelines and Limitations (Azure SQL Database) Q: What's the maximum size of a SQL Server running in a Virtual Machine? A: The maximum Windows Azure VM has currently 8 CPU cores, 14 or 56 GB of RAM and 16x 1 TB hard drives. More details here: Virtual Machine and Cloud Service Sizes for Azure Q: How can we register for Windows Azure? A: Mauritius is currently not listed for phone verification. Please get in touch with Arnaud Meslier at Microsoft IOI & FP Q: Can I use my own domain name for Windows Azure websites? A: Yes, you can. But this might require to upscale your account to Standard. In case that I missed a question and answer, please use the comment section at the end of the article. Thanks! Final results Every participant was instructed during the hands-on-lab session on how to set up a cloud computing service in their account. Of course, I won't keep the results from you... Global Azure Lab GWAB 2014: Our cloud computing contribution to the research of diabetes And I would say Mauritius did a good job! Upcoming Events What are the upcoming events here in Mauritius? So far, we have the following ones (incomplete list as usual) in chronological order: Launch of Microsoft SQL Server 2014 (15.4.2014) Corsair Hackers Reboot (19.4.2014) WebCup (TBA ~ June 2014) Developers Conference (TBA ~ July 2014) Linuxfest 2014 (TBA ~ November 2014) Hopefully, there will be more announcements during the next couple of weeks and months. If you know about any other event, like a bootcamp, a code challenge or hackathon here in Mauritius, please drop me a note in the comment section below this article. Thanks! Networking and job/project opportunities Despite having technical presentations on Windows Azure an event like this always offers a great bunch of possibilities and opportunities to get in touch with new people in IT, have an exchange of experience with other like-minded people. As I already wrote about Communities - The importance of exchange and discussion - I had a great conversation with representatives of the University des Mascareignes which are currently embracing cloud infrastructure and cloud computing for their various campuses in the Indian Ocean. As for the MSCC it would be a great experience to stay in touch with them, and to work on upcoming, common activities. Furthermore, I had a very good conversation with Thierry and Ludovic of Microsoft IOI & FP on the necessity of user groups and IT communities here on the island. It's great to see that the MSCC is currently on a run and that local companies are sharing our thoughts on promoting IT careers and exchange of IT knowledge in such an open way. I'm also looking forward to be able to participate and to contribute on more events in the near future. My resume of the day We learned a lot today and there is always room for improvement! It was an awesome event and quite frankly it was a pleasure to spend the day with so many enthuastic IT people in the same room. It was a great experience to organise such event locally and participate on a global scale to support the GlyQ-IQ Technology in their research on diabetes. I was so pleased to see the involvement of new MSCC members in taking the opportunity to share and learn about the power of cloud computing. The Mauritius Software Craftsmanship Community is on the right way and this year's bootcamp on Windows Azure only marked the beginning of our journey. Thank you to our sponsors and my kudos to the MSPs! Update: Media coverage The event has been reported in local media, too. Following are some resources: Orange - Local - Business: Le cloud, pour des recherches approfondies sur le diabète Maurice Info.mu: Le cloud, pour des recherches approfondies sur le diabète Le Quotidien Pg 2: Global Windows Azure Bootcamp 2014 - Le cloud pour des recherches approfondies sur le diabète The Observer Pg 12: Le cloud, pour des recherches approfondies sur le diabète if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-global-windows-azure-bootcamp.html'; this.page.identifier = '9271ed1c97_idmscc-global-windows-azure-bootcamp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-scripting-administrators-toolbox-of-magic.html": {
    "href": "mscc-scripting-administrators-toolbox-of-magic.html",
    "title": "MSCC: Scripting - Administrator's­ toolbox of magic... - Get Blogged by JoKi",
    "summary": "MSCC: Scripting - Administrator's­ toolbox of magic... Copy Markdown View Markdown GitHub Markdown Source MSCC: Scripting - Administrator's­ toolbox of magic... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-scripting-administrators-toolbox-of-magic'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-scripting-administrators-toolbox-of-magic'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Scripting - Administrator's­ toolbox of magic...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-05-04 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Finally, we made it to have our April meetup - in May. The most obvious explanation is the increased amount of open source and IT activities that either the MSCC, the Linux User Group of Mauritius (LUGM), or the University of Mauritius Student's Computer Club is organising. It's absolutely incredible to see the recent hype of events here on the island. And I'm loving it! Unfortunately, we also had to deal with arranging for a location this time. It was kind of an odyssey as my requests (and phone calls) haven't been answered, even though I tried it several times - well, kind of disappointing and I have to look into that for future gatherings. In my opinion, it is essential that two parameters of a community meeting are fixed as early as possible: Location, and Date and time You can't just change one or both on the very last minute. Well, this time we had to do it due to unforeseen reasons, and I apologise to any MSCC member which couldn't make it to our April meetup. Okay, lesson learned but now back to the actual meetup report ... Shortly after the meeting I placed the following statement as my first impression: \"Spontaneous and improvised \uD83D\uDE03 No, seriously, Ish and Dan had well prepared presentations on shell scripting, mainly focused towards Bourne Again Shell (bash), and the pros and cons of scripting versus actually writing something in a decent programming language. I thought that I could cut myself out of the equation but the demand for information about PowerShell was higher than expected...\" Well, it turned out that the interest in Windows PowerShell was high, as I even got a couple of questions on it via social media networks during the evening. I also like to mention that the number of attendees went back to what I would call a \"standard\" number of participation. This time there were 12 craftsmen, but again a good number of First Timers. Reactions of other attendees Here are some impressions and feedback from our participants: \"Enjoyed the bash and powershell (linux / windows) presentations ...\" -- Nadim on event comments \"He [Daniel] also showed us some syntax loopholes in Bash that could leave someone with bad code.\" -- Ish on MSCC – Let's talk about Scripting Glad to see a couple of first time attendees, especially students from the university itself. Some details on the presentations MSCC: First time visit at the University of Mauritius - Phase II Engineering Tower, room 2.9 Gimme some love ... bash and other shells Ish gave a great introduction into shell scripting as he spoke about existing shell environments and a little bit about their history. Furthermore, he talked about various built-in commands, the use of coreutils, the ability to daisy-chain multiple commands using pipes, the importance of the standard I/O streams and their file descriptors in advanced scripting techniques. Combined with a couple of sample statements in the Linux terminal on Ubuntu 14.04 machine it was a solid presentation. Have a closer look at his slides - published on his blog on MSCC – Let's talk about Scripting. Oddities of scripting After the brief introduction into bash it was Daniel's turn to highlight a good number of oddities when working with shell scripts. First of all, it should be clear that scripting is not supposed for any kind of implementations in terms of software but simply to automate administrative procedures and to simplify routine jobs on a system. One of the cool oddities that he mentioned is that everything (!) in a shell is represented by strings; there are no other types like integer, float, date-time, etc. that you'd like to use in a full-fledged programming language. Let's have a look at his sample: more to come... What's the output? As a conclusion, Daniel suggests that shell scripting should be limited but not restricted to automatic repetitive command stacks and batch jobs, startup wrapper for applications in order to set up the execution environment, and other not too sophisticated jobs. But as soon as it might involve a little bit more logic or you might rely on performance it's better to write an application in Ruby, Python, or Perl (among others of course). This is also enables the possibility to test your code properly. MSCC: Ish talking about Bourne Again Shell (bash) and shell scripting to automate regular tasks MSCC: Daniel gives an overview about the pros and cons of shell scripting versus programming MSCC: PowerShell as your scripting solution on Windows operating systems The path of the Enlightened is long ... and tough. Honestly, even though PowerShell was mentioned without any further details on the meetup's agenda, I didn't expect that there would be demand to give a presentation on Microsoft PowerShell after all. I already took this topic out of the announcement but the audience wanted to have some information. Okay, then let's see what I could do - improvised style. While my machine booted and got hooked up to the projector, I started to talk about the beginnings of PowerShell from back in 2006, and its predecessors MS DOS and Command Prompt. A throwback in history... always good for young people. As usual, Microsoft didn't get it at that time. Instead of listening to their client's needs and demands they ignored the feasibility to administrate Windows server farms without any UI tools. PowerShell is actually a result of this, and seeing that shell scripting is a common, reliable and fast way in an administrator's toolbox for decades, Microsoft had to adapt from their Microsoft Management Console (MMC) to a broader approach. It's not like shell scripting was something new; it is in daily use by alternative operating systems like AIX, HP UX, Solaris, and last but not least Linux. Most interestingly, Microsoft is very good at renovating existing architectures, and over the years PowerShell not only replaced their own combination of Command Prompt and Scripting Hosts (VBScript and CScript) but really turned into a challenging competitor on the market. The shell is easy to extend with cmdlets, and open to other Microsoft products like SQL Server, SharePoint, as well as Third-party software applications. Similar to MMC PowerShell also offers the ability to administer other machine remotely - only without a graphical user interface and therefore it's easier to automate and schedule regular tasks. Following is a sample of a PowerShell script file (extension .ps1): $strComputer = \".\" $colItems = get-wmiobject -class Win32_BIOS -namespace root\\CIMV2 -comp $strComputer foreach ($objItem in $colItems) { write-host \"BIOS Characteristics: \" $objItem.BiosCharacteristics write-host \"BIOS Version: \" $objItem.BIOSVersion write-host \"Build Number: \" $objItem.BuildNumber write-host \"Caption: \" $objItem.Caption write-host \"Code Set: \" $objItem.CodeSet write-host \"Current Language: \" $objItem.CurrentLanguage write-host \"Description: \" $objItem.Description write-host \"Identification Code: \" $objItem.IdentificationCode write-host \"Installable Languages: \" $objItem.InstallableLanguages write-host \"Installation Date: \" $objItem.InstallDate write-host \"Language Edition: \" $objItem.LanguageEdition write-host \"List Of Languages: \" $objItem.ListOfLanguages write-host \"Manufacturer: \" $objItem.Manufacturer write-host \"Name: \" $objItem.Name write-host \"Other Target Operating System: \" $objItem.OtherTargetOS write-host \"Primary BIOS: \" $objItem.PrimaryBIOS write-host \"Release Date: \" $objItem.ReleaseDate write-host \"Serial Number: \" $objItem.SerialNumber write-host \"SMBIOS BIOS Version: \" $objItem.SMBIOSBIOSVersion write-host \"SMBIOS Major Version: \" $objItem.SMBIOSMajorVersion write-host \"SMBIOS Minor Version: \" $objItem.SMBIOSMinorVersion write-host \"SMBIOS Present: \" $objItem.SMBIOSPresent write-host \"Software Element ID: \" $objItem.SoftwareElementID write-host \"Software Element State: \" $objItem.SoftwareElementState write-host \"Status: \" $objItem.Status write-host \"Target Operating System: \" $objItem.TargetOperatingSystem write-host \"Version: \" $objItem.Version write-host } Which gives you information about your BIOS and Windows OS. Then change the computer name to another one on your network (NetBIOS based) and run the script again. There lots of samples and tutorials at the Microsoft Script Center, and I would advise you to pay a visit over there if you are more interested in PowerShell. The Script Center provides the download links, too. Upcoming Events What are the upcoming events here in Mauritius? So far, we have the following ones (incomplete list as usual) in chronological order: Hacking Defence (14. May 2014) WebCup Maurice (7. & 8. June 2014) Developers Conference (TBA ~ July 2014) Linuxfest 2014 (TBA ~ November 2014) Hopefully, there will be more announcements during the next couple of weeks and months. If you know about any other event, like a bootcamp, a code challenge or hackathon here in Mauritius, please drop me a note in the comment section below this article. Thanks! My resume of the day Spontaneous and improvised \uD83D\uDE03 The new location at the University of Mauritius turned out very well, there is plenty of space, and it could be a good choice for future meetings. Especially, having the ability to get more and more students into our IT community sounds like a great opportunity. Later during the day, I got some promising mails from Nadim regarding future sessions at the local branch of the Middlesex University. Well, we will see in the future... But for now this will be on hold until approximately October when students resume their regular studies. Anyway, it was a good experience at the university, and thanks again to the UoM Student's Computer Club that made the necessary arrangements for the MSCC! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-scripting-administrators-toolbox-of-magic.html'; this.page.identifier = '9271ed1c97_idmscc-scripting-administrators-toolbox-of-magic'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-version-control-systems-git-tfs-vso.html": {
    "href": "mscc-version-control-systems-git-tfs-vso.html",
    "title": "MSCC: Purpose and benefits of Version Control Systems (VCS) - Get Blogged by JoKi",
    "summary": "MSCC: Purpose and benefits of Version Control Systems (VCS) Copy Markdown View Markdown GitHub Markdown Source MSCC: Purpose and benefits of Version Control Systems (VCS) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-version-control-systems-git-tfs-vso'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-version-control-systems-git-tfs-vso'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Purpose and benefits of Version Control Systems (VCS)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-06-18 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); You're working in IT and not using any kind of version control system? Sorry, then you're doing something wrong! RSVP for MSCC meetup of June This month's meetup will be an introduction into the mechanics of version control systems (VCS) like git, Mercurial, TFS, and others in general. VCS are not optional but compulsory in any area of IT. Whether you're developing source code for the next buzz app, writing SQL scripts for your database, or automating your administrative tasks with shell scripts it's better to have a \"time machine\" in order to keep multiple version, stay organised and leverage the power of differences. git - a modern approach to VCS - Nayar Nayar is going to give us a brief overview of the basic principles of working with git. Which are the necessary steps to get started and which are the usual commands in order to get the most out of git. Visual Studio Online (VSO) - Jochen Are you mainly rooted on the Windows platform and looking for a good alternative to Team Foundation Services (TFS), then VSO might give you hand at achieving this. Similar to git VSO is an open infrastructure but plays very well together with the Microsoft Azure cloud infrastructure. Recent and upcoming events in Mauritius Let's have a chat about recent events like WebCup 2014 or Emtel Knowledge Series and have a head start on upcoming events like Code Challenge, and others to come. Networking and general discussions Of course, there will be plenty of time to chat and exchange with other like-minded craftsmen. Bring your topics and discuss various issues with other professionals. Share your experience and use the ability to learn from others. Looking forward to meet soon. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-version-control-systems-git-tfs-vso.html'; this.page.identifier = '9271ed1c97_idmscc-version-control-systems-git-tfs-vso'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc-wordpress-an-introduction.html": {
    "href": "mscc-wordpress-an-introduction.html",
    "title": "MSCC: WordPress - An Introduction - Get Blogged by JoKi",
    "summary": "MSCC: WordPress - An Introduction Copy Markdown View Markdown GitHub Markdown Source MSCC: WordPress - An Introduction Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-wordpress-an-introduction'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc-wordpress-an-introduction'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: WordPress - An Introduction') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-02-28 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Having more sessions from our very own community members seems to be the right way to go. After last month's session on HTML5, CSS3 and a bit of JavaScript, our craftman Rikesh voluntarily gave his presentation on WordPress - An Introduction. Hell yeah! Depending on what you might consider as an introduction but this was a full-featured introduction for developers into the WordPress CMS platform. We were not simply talking about how to sign up on wordpress.com and start blogging about trivial things. No way... Rikesh went from best practices during installation, to some good advice for the initial configuration and then over to the inerts and depths of plugin development. And more inportant... We were able to match and even exceed the number of attendees this month: 30 people were present! As usual, here's my first impression about the MSCC meetup: \"Kudos to Rikesh for this great introduction into WordPress - actually, it was more a developer's jumpstart than a brief overview. Highly appreciated and looking for more stuff like this!\" As mentioned initially, I think that we are absolutely on the right track to organise our meetups with sessions of our craftsmen. There are so many good ideas and topics available; almost an endless pool of possibilities. Reactions of other attendees As usual, I'm always interested in the opinions from other craftsmen. Following some statements found online: \"Thanks @MSCraftsman @rrikesh for today's meetup. Well done.\" -- Paul on Twitter \"Thanks for the talk--great introduction for developers indeed!\" -- Sebastian on Meetup \"Au fait, la présentation était une connaissance fondée très riche et purement pour les codeurs et les débutants d'avoir un aperçu sur la réalité de WordPress.\" -- Nitin on L’éblouissante WordPress en démo au #MSCC \"The part when Rikesh explained about Hooks, namely filter and action caught my attention. WordPress Codex is a great resource but might appear bulky to a non-developer who only is looking for a couple of tweaks. So, this explanation comes handy next time I'm on a WordPress hack.\" -- Ish on geeksMauritius Software Craftsmanship Community (aka User Group):~$ Hello, WordPress Also check out the comments and feedback on our Talking about WordPress and development According to the usual statistics online (Source: W3Techs) WordPress is the most used blogging platform or content management system (CMS) world-wide. Although, it all started as a blogging platform the system is absolutely not limited to this functionality, and you can easily use it for corporate websites, online forums, ecommerce platforms, and even for the development of Web Apps. Due to this broad usage of WordPress as a web platform in all kind of fields, it is also obvious that the job market has a higher demand for WordPress developers than for any other CMS platform. You can easily verify this on various freelancer portals. The number of job and project listings for WordPress is easily 10 to 20 times higher than for Drupal, Joomla, or Magento. As an absolute novice to WordPress you can sign up for free on WordPress.com and you get your web site up and running in less than 10 minutes. In my opinion that's the easiest and fastest way to start your experience in the blogosphere, and to boost your career in IT. Later on, you might consider to extend and customise your site. Then navigate over to WordPress.org and the Codex repository of extensions, get a fresh copy and install it on either your own root server or on a hosted solution. Even running it on Windows Azure Web Services is quite convienent since WordPress version 3.8. Just one good advice, only take the available packages from the official website... Otherwise, you might be doomed with exploits \uD83D\uDE03 As I tweeted several times during the presentation it was very interesting to see the differences in concept and development between WordPress and Joomla!. Some aspects are simplified compared to Joomla, like shorter query parameters for search, posts and other elements. Others, like the customisation or extension of core features are following a completely different approach. Similar to Joomla!'s Output Overrides in order to change behaviour of controller or how HTML output is generated WordPress relies on functional polymorphism to achieve similar results. Most important, the proper and consequent use of 'function_exists()' calls in your own development. Rikesh also explained that you shouldn't touch any of the core components as they might be overwritten with the next update and your modifications will be deleted. Using the concept of 'child templates' helps to separate your code from other developers functionality. This simply works because WordPress executes and evaluates the PHP files from inside out, read: from child templates to parent templates to the core systems. The earlier a function is written and all subsequent location properly use function_exists() calls it will guarantee that your inner code is executed rather than something later in the execution stack. Rikesh also described and demo'd very clearly that the extensibility of WordPress is commonly based on hooks. There are actually two main types: actions and filters. Which have different behaviours in terms of content handling. I won't go too much into the details for now but rather advice you to have a look at the material provided by Rikesh. The complete presentation is available on SlideShare, or you might go through the slides here: *[Hello WordPress](https://www.slideshare.net/rrikesh/hello-wordpress \"Hello WordPress\") from [Rikesh Ramlochund](https://www.slideshare.net/rrikesh)* And Rikesh also made all the code samples available on Github: https://github.com/rrikesh/hello-wordpress-mscc The Q&A session after the presentation was great, too. Very good questions, all knowingly answered by Rikesh. And of course, you get in touch with him through various channels in case that you either have some more questions regarding WordPress usage and development, or maybe you'd like to hire him for some jobs. Feel free in any case... Following some more impressions of the day: Rikesh demonstrates the possibilities of WordPress configuration in the dashboard Captivated audience following the demos and code samples Conference room full of MSCC craftsmen - again a good number of first time attendees Thanks to our uploaders! Courtesy of pictures is theirs of course! Events What are the upcoming events here in Mauritius? So far, we have the following ones (incomplete list as usual) in chronological order: Corsair Hackers Reboot (15.3.2014) Global Windows Azure Bootcamp (29.3.2014) Innovation Day(s) (4.4.2014) WebCup (TBA ~ June 2014) Developers Conference (TBA ~ July 2014) Linuxfest 2014 (TBA ~ November 2014) Hopefully, there will be more announcements during the next couple of weeks and months. If you know about any other event, like a bootcamp, a code challenge or hackathon here in Mauritius, please drop me a note in the comment section below this article. Thanks! Networking and job/project opportunities At the begin of February it was officially announced that the Mauritian government offers a customised service to empower local small and medium enterprises with an online presence. Following the essential statement of the Web Builder: \"Web Builder is a project initiated by the Ministry of Finance & Economic Development to empower small & medium enterprises (SMEs) by providing a service for the creation of their websites. The service will be offered through the Ebène Accelerator and Port-Louis Accelerator (opening soon) in collaboration with Mauritius Telecom.\" -- Source: Ebene Accelerator We had a little chat about that and quite frankly the opinions on this initiative are wide-spread and controversial. As usual, our craftsmen use any free minute to have vivid conversations on recent activities in ICT and in-between I could overhear a couple of indirect job and project discussions. Which makes me very happy actually. Having this kind of exchange is exactly what I wanted to achieve, and it's great to see it happen on every occasion. Once again, we had a couple of first-time attendees and with the young entrepreneurs Johann and Paul of SleepOut.com there were good conversations on. Also, I would like to thank Sebastian Kippe of 5apps.com for his generous giveaways in form of geeky stickers on JavaScript, HTML5 and very own App deployment service. Got some nice swag at the recent meeting of the Mauritius Software Craftsmanship Community. Thanks to Sebastian Kippe. My resume of the day More than expected! As stated initially, I'm very pleased by the presentation on WordPress done by Rikesh. He did a great job with his story line, and kept the audience live and alive with the general aspects of using WordPress, some essential details on installation and configuration and last but not least about further customisation with plugin development. I'm looking forward to get more information about WordPress in a future event. Maybe some more details on how to create themes... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc-wordpress-an-introduction.html'; this.page.identifier = '9271ed1c97_idmscc-wordpress-an-introduction'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mscc.html": {
    "href": "mscc.html",
    "title": "Mauritius Software Craftsmanship Community (aka User Group) - Get Blogged by JoKi",
    "summary": "Mauritius Software Craftsmanship Community (aka User Group) Copy Markdown View Markdown GitHub Markdown Source Mauritius Software Craftsmanship Community (aka User Group) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mscc'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Mauritius Software Craftsmanship Community (aka User Group)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-07-01 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); There we go! I finally managed to push myself forward and pick up an old, actually too old, idea since I ever arrived here in Mauritius more than six years ago. I'm talking about a community for all kind of ICT connected people. In the past (back in Germany), I used to be involved in various community activities. For example, I was part of the Microsoft Community Leader/Influencer Program (CLIP) due to an FAQ website on Visual FoxPro, actually Active FoxPro Pages (AFP) to be more precise. Then in 2003/2004 I addressed the responsible person of the dFPUG user group in Speyer in order to assist him in organising monthly user group meetings. Well, he handed over management completely, and attended our meetings regularly. Why did it take you so long? Well, I don't want to bother you with the details but short version is that I was too busy on either job (building up new companies) or private life (got married and we have two lovely children, eh 'monsters') or even both. But now is the time where I was starting to look for new fields of activity; given the fact that I gained some spare time. My businesses are up and running, the kids are in school, and I am finally in a position where I can commit myself to community again. And I love to do that! Why a new user group? Good question... And 'easy' to answer. Since back in 2007 I did my usual research, eh Google searches, to see whether there are existing user groups in Mauritius and in which field of interest. And yes, there are! If I recall this correctly, then there are communities for Adobe, PHP, Drupal, Python (just recently), Oracle, Java, .NET, and Linux (which used to be even two). But... either they do not exist anymore, they are dormant, or there is only a low heart-beat, frankly speaking. And yes, I went to meetings of the Linux User Group Meta (Mauritius) back in 2010/2011 and just recently. I really like the setup and the way the LUGM is organised. It's just that I have a slightly different point of view on how a user group or community should organise itself and how to approach future members. Don't get me wrong, I'm not criticizing others doing a very good job, I'm only saying that I'd like to do it differently. The last meeting of the LUGM was awesome; read my feedback about it. Ok, so what's up with 'Mauritius Software Craftsmanship Community' or short: MSCC? As I've already written in my article on 'Communities - The importance of exchange and discussion' I think it is essential in a world of IT to stay 'connected' with a good number of other people in the same field. There is so much dynamic and every day's news that it is almost impossible to keep on track with all of them. The MSCC is going to provide a common platform to exchange experience and share knowledge between each other. You might be a newbie and want to know what to expect working as a software developer, or as a database administrator, or maybe as an IT systems administrator, or you're an experienced geek that loves to share your ideas or solutions that you implemented to solve a specific problem, or you're the business (or HR) guy that is looking for 'fresh' blood to enforce your existing team. Or... you're just interested and you'd like to communicate with like-minded people. Meetup of 26.06.2013 @ L'arabica: Of course there are laptops around. Free WiFi, power outlet, coffee, code and Linux in one go. The MSCC is technology-agnostic and spans an umbrella over any kind of technology. Simply because you can't ignore other technologies anymore in a connected IT world as we have. A front-end developer for iOS applications should have the chance to connect with a Python back-end coder and eventually with a DBA for MySQL or PostgreSQL and exchange their experience. Furthermore, I'm a huge fan of cross-platform development, and it is very pleasant to have pure Web developers - with all that HTML5, CSS3, JavaScript and JS libraries stuff - and passionate C# or Java coders at the same table. This diversity of knowledge can assist and boost your personal situation. And last but not least, there are projects and open positions 'flying' around... People might like to hear others opinion about an employer or get new impulses on how to tackle down an issue at their workspace, etc. This is about community. And that's how I see the MSCC in general - free of any limitations be it by programming language or technology. Having the chance to exchange experience and to discuss certain aspects of technology saves you time and money, and it's a pleasure to enjoy. Compared to dusty books and remote online resources. It's human! Organising meetups (meetings, get-together, gatherings - you name it!) As of writing this article, the MSCC is currently meeting every Wednesday for the weekly 'Code & Coffee' session at various locations (suggestions are welcome!) in Mauritius. This might change in the future eventually but especially at the beginning I think it is very important to create awareness in the Mauritian IT world. Yes, we are here! Come and join us! \uD83D\uDE09 The MSCC's main online presence is located at Meetup.com because it allows me to handle the organisation of events and meeting appointments very easily, and any member can have a look who else is involved so that an exchange of contacts is given at any time. In combination with the other entities (G+ Communities, FB Pages or in Groups) I advertise and manage all future activities here: Mauritius Software Craftsmanship Community This is a community for those who care and are proud of what they do. For those developers, regardless how experienced they are, who want to improve and master their craft. This is a community for those who believe that being average is just not good enough. I know, there are not many 'craftsmen' yet but it's a start... Let's see how it looks like by the end of the year. There are free smartphone apps for Android and iOS from Meetup.com that allow you to keep track of meetings and to stay informed on latest updates. And last but not least, there is a Trello workspace to collect and share ideas and provide downloads of slides, etc. Trello is also available as free smartphone app. Sharing is caring! As mentioned, the #MSCC is present in various social media networks in order to cover as many people as possible here in Mauritius. Following is an overview of the current networks: Twitter - Latest updates and quickies Google+ - Community channel Facebook - Community Page LinkedIn - Community Group Trello - Collaboration workspace to share and develop ideas Hopefully, this covers the majority of computer-related people in Mauritius. Please spread the word about the #MSCC between your colleagues, your friends and other interested 'geeks'. Your future looks bright Running and participating in a user group or any kind of community usually provides quite a number of advantages for anyone. On the one side it is very joyful for me to organise appointments and get in touch with people that might be interested to present a little demo of their projects or their recent problems they had to tackle down, and on the other side there are lots of companies that have various support programs or sponsorships especially tailored for user groups. At the moment, I already have a couple of gimmicks that I would like to hand out in small contests or raffles during one of the upcoming meetings, and as said, companies provide all kind of goodies, books free of charge, or sometimes even licenses for communities. Meeting other software developers or IT guys also opens up your point of view on the local market and there might be interesting projects or job offers available, too. A community like the Mauritius Software Craftsmanship Community is great for freelancers, self-employed, students and of course employees. Meetings will be organised on a regular basis, and I'm open to all kind of suggestions from you. Please leave a comment here in blog or join the conversations in the above mentioned social networks. Let's get this community up and running, my fellow Mauritians! Recent updates The MSCC is now officially participating in the O'Reilly UK User Group programm and we are allowed to request review or recension copies of recent titles. Additionally, we have a discount code for any books or ebooks that you might like to order on shop.oreilly.com. More applications for user group sponsorship programms are pending and I'm looking forward to a couple of announcement very soon. And... we need some kind of 'corporate identity' - Over at the MSCC website there is a call for action (or better said a contest with prizes) to create a unique design for the MSCC. This would include a decent colour palette, a logo, graphical banners for Meetup, Google+, Facebook, LinkedIn, etc. and of course badges for our craftsmen to add to their personal blogs and websites. Please spread the word and contribute. Thanks! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mscc.html'; this.page.identifier = '9271ed1c97_idmscc'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "msdn-visual-foxpro-mit-visual-studio-2005.html": {
    "href": "msdn-visual-foxpro-mit-visual-studio-2005.html",
    "title": "MSDN: Visual FoxPro mit Visual Studio 2005 - Get Blogged by JoKi",
    "summary": "MSDN: Visual FoxPro mit Visual Studio 2005 Copy Markdown View Markdown GitHub Markdown Source MSDN: Visual FoxPro mit Visual Studio 2005 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'msdn-visual-foxpro-mit-visual-studio-2005'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'msdn-visual-foxpro-mit-visual-studio-2005'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSDN: Visual FoxPro mit Visual Studio 2005') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-20 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); YES! MSDN: Visual FoxPro mit Visual Studio 2005 .o( Ansonsten sprachlos! ) if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'msdn-visual-foxpro-mit-visual-studio-2005.html'; this.page.identifier = '9271ed1c97_idmsdn-visual-foxpro-mit-visual-studio-2005'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "msdn-webcast-serie-zu-visual-foxpro-9-0.html": {
    "href": "msdn-webcast-serie-zu-visual-foxpro-9-0.html",
    "title": "MSDN-Webcast-Serie zu Visual FoxPro 9.0 - Get Blogged by JoKi",
    "summary": "MSDN-Webcast-Serie zu Visual FoxPro 9.0 Copy Markdown View Markdown GitHub Markdown Source MSDN-Webcast-Serie zu Visual FoxPro 9.0 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'msdn-webcast-serie-zu-visual-foxpro-9-0'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'msdn-webcast-serie-zu-visual-foxpro-9-0'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSDN-Webcast-Serie zu Visual FoxPro 9.0') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-10 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Cool, kam heute Mittag über den MSDN Newsletter rein: *MSDN-Webcast-Serien Webcast-Serien - das sind Informationen zu einem Thema auf den Punkt gebracht. Informieren Sie sich, welche Serien geplant sind und welche Referenten die Themen behandeln. Patterns im Einsatz Visual FoxPro 9 * Damit hat man die wichtigen Werkzeuge für den Alltag im Blick: Design Patterns und natürlich Visual FoxPro 9.0. Ich werde künftig die Webcasts zu Visual FoxPro in meine Ressourcen- und Quellenhinweise aufnehmen. Das Engagement der deutschsprachigen FoxPro Usergroup bzw. von Rainer Becker ist bemerkenswert. Ich bin auch mal gespannt auf die Entwicklerkonferenz. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'msdn-webcast-serie-zu-visual-foxpro-9-0.html'; this.page.identifier = '9271ed1c97_idmsdn-webcast-serie-zu-visual-foxpro-9-0'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "msdn-zu-vfp-ole-db-provider.html": {
    "href": "msdn-zu-vfp-ole-db-provider.html",
    "title": "MSDN zu VFP OLE DB Provider - Get Blogged by JoKi",
    "summary": "MSDN zu VFP OLE DB Provider Copy Markdown View Markdown GitHub Markdown Source MSDN zu VFP OLE DB Provider Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'msdn-zu-vfp-ole-db-provider'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'msdn-zu-vfp-ole-db-provider'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSDN zu VFP OLE DB Provider') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-12 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ein guter Einstieg bietet die MSDN dazu: https://msdn.microsoft.com/library/default.asp?url=/library/en-us/fox7help/html/dggrfOLEDBProviderforVisualFoxPro.asp if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'msdn-zu-vfp-ole-db-provider.html'; this.page.identifier = '9271ed1c97_idmsdn-zu-vfp-ole-db-provider'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "mssc-16112013-clean-code-developer-your-workspace.html": {
    "href": "mssc-16112013-clean-code-developer-your-workspace.html",
    "title": "MSCC: Clean Code Development & Your workspace - Get Blogged by JoKi",
    "summary": "MSCC: Clean Code Development & Your workspace Copy Markdown View Markdown GitHub Markdown Source MSCC: Clean Code Development & Your workspace Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mssc-16112013-clean-code-developer-your-workspace'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'mssc-16112013-clean-code-developer-your-workspace'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MSCC: Clean Code Development & Your workspace') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-11-18 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Last weekend the Mauritius Software Craftsmanship Community (MSCC) had been organising their latest monthly meetup. Once again, we met at the Ebene Accelerator, Orange Tower, Ebene. \"Thank You to Ebene Accelerator for offering us the use of their conference rooms. It's really helpful and we like to be here.\" In this article I would like to take the chance to quickly summarize the topics we covered and the ideas we worked out for upcoming meetings. And it was also pleasant to welcome more 'first-timers' this month. Reactions from other craftsmen Funnily, it seems that there is kind of gentleman's agreement active at the moment which says that this month's community activities should be blogged in French language. Frankly speaking I leave that exercise to my native craftsmen. It would be more than horrible writing this entry in French language - both, for me to write and for you to read. \uD83D\uDE09 \"Comme cela faisait parti des topiques d’aujourd’hui on s’est partagé les photos de nos desks (bureau). On a ainsi eu une longue discussion sur les bonnes pratiques de “Clean Desk Policy” … Quand à moi je me suis défendu pourquoi j’aime mon “messy desk” \uD83D\uDE03\" -- Ish on MSCC – L’interopérabilité \uD83D\uDE03 Looking forward to add more links as soon as they'll be available online... Latest MSCC updates Since founding this community back in May 2013 it has been a great journey so far. Our weekly and of course our monthly meetups have been great fun and serious exchange of experience. This month we are going to pass the 6-month mark... And yes it's on the one hand quite remarkable to see what has been achieved during this short period and on the other hand I'm very happy to say that the #MSCC is hopefully not following all those unfortunate user groups that have been alive in Mauritius. Touching wood, the #MSCC is currently the most active IT community and we are working towards more members on our official website over at Meetup.com, and we are looking forward to encourage others to either re-animate their activities or to join us willingly. Based on an initiative of Yann, we started to organise small meetings with local IT companies in order to create more awareness about the MSCC in general but also to see whether there might be common interest and synergy effects. Up till now, we already had a conversation with representatives of Ceridian and Talents. Both companies seem to like our concept, ideas and mid-term plans about enforcing the IT landscape in Mauritius. Very positive for us is also that they both generously offered their facilities and eventually guest sessions for next meetups. It would be awesome to have the ability to introduce our community to more software developers, system administrators or DevOps during the next couple of month. Actually, we are very ambitious to reach 100+ craftsmen before the end of 2013. Dear reader, did you already join the MSCC? No, it's for free and anyone is welcome on our official site. Thanks! Typical setup: Relaxed and non-formal environment during our monthly MSCC meetups. Clean Code Developer Although announced as topic for this month, we simply skipped it. And I think it was completely okay to do so. Anyway, it's only post-poned and it's going to be a more practical session next time. As we already spoke about Red and Orange Grades, I would like to provide a number of source code snippets that we can analyse, discuss and refactor together. So, hold the line... we'll back in a moment. Your workspace (desk, OS, apps, tools, etc) Working in IT usually means that you are sitting most of your time in front of your work desk. I mean, that's your profession day in, day out - something between 6 and 12 hours per day, 5 or even 6 days week, and so on. Having a proper working environment is crucial for your productivity and efficiency after all. \"If you're a developer, you need to spend money on a great computer, an awesome monitor, a fantastic chair and a good bed\" -- Scott Hanselman In those matters, I would like to refer to the 4 B's - Brain, Bytes, Back, Buns - The Programmer's Priorities - written by Scott Hanselman two years back, as well as a more recent article here on this blog: Essentials for Software Developer or any computer job. Due to my work as a IT consultant and working with a number of IT companies here in Mauritius, I clearly tell that the ergonomically aspects of a proper work environment are non-existent. Software developers, database administrators, system integrators, you name it are quite commonly sitting on the cheapest piece of furniture a company might be able to purchase here on the island. The major problem in most cases is the number of employees or ,frankly speaking, the lack of budget in order to provide comfort for the powerhouse workers. And same usually applies to the hardware equipment given to employees. Usually, just the cheapest stuff available and absolutely no chance for anyone to ask for replacement. Unfortunately, in some cases I also discovered that a developer brought her own hardware, like a high-quality keyboard and high-resolution optical mouse from home to her office work desk. Well, prior to the meetup I already requested our craftsmen to submit their pictures and we talked about bits and pieces. One really positive effect that we discovered is clearly that having at least two monitors at your workspace automatically increases ones productivity. No need to switch between apps via hot-keys or fiddling around with resizing and positioning of application windows. And, adding a second monitor is absolutely affordable for anyone, even here in Mauritius. I pulled my portable USB monitor from the laptop bag and passed it around. The overall reaction was very positive and some were even impressed by the light-weight of the screen. Eventually, I should get in touch VEngineers about commission \uD83D\uDE03 Following an incomplete set of work desks we had a look at and talked about. Work space of Yann Work space of Nirvan Work space of Nadim My work space More pictures of our craftsmen's workspaces and in general of our recent meetup are available in the photo gallery. Most interestingly, there is a repetitive pattern in the hardware: multiple screens, high quality peripherals and head-phones or head-set. Oh, and don't worry about hardware limitations... Using a software KVM like Synergy gives you the ability to share and use one set of peripherals across multiple machines independent of the operating system you're using. So, grab the old rusty PC or your previous laptop from the shelf and hook them up. Events What are the upcoming events here in Mauritius? So far, we have the following ones (incomplete list as usual) in chronological order: Virtual launch of Microsoft Visual Studio (19.11.2013) InfoTech 2013 (21. - 24.11.2013) Linuxfest 2013 (25.11.2013) Code Challenge (TAB ~ Feb 2014) Innovation Day(s) (4.4.2014) Developers Conference (TBA ~ June 2014) Hopefully, there will be more announcements during the next couple of weeks and months. One of the hottest candidates that I would like to have here in Mauritius would be Microsoft BootCamp and Windows 8.1 and/or Windows Phone 8 development. Networking and job/project opportunities Having a group of like-minded people with a sense of passion and proud in their work is always a good igniter for interesting conversation. And so this time again, we spoke about the importance of knowing multiple programming languages. The ones that you might have learned during your years at the university, like C++ or Java, aren't necessarily the ones you are going to earn your money and fame with. Dan had some good arguments that even though he used to learn C/C++ in his youth, he is mainly working in Ruby (on Rails) and other web-related technologies. On my side, I have to say that I 'outed' myself - even so in a funny way (carefully: written in German language). I mentioned that it is pretty interesting to observe the trends during the last 2-3 years on JavaScript. Before I started to develop data-driven desktop applications in Visual FoxPro, I used to hack down dynamic web sites in HTML 3.x, Perl, mSQL/MySQL and JavaScript. Some MSCC youngsters seem to be surprised about the fact that JavaScript is almost 20 years old - A Short History of JavaScript. And I used to code back in those early days... Anyway, the important information is: You have to know more than 2 or even more than 3 programming languages nowadays. And JavaScript should be part of that. My resume of the day Digging in the past... Oh yes, it was great fun to take a deep dive into Web development during the last millennium. For one of our future meetups, I really looking forward to have someone talking about either jQuery, AngularJS, node.js, or any other JavaScript library that is commonly used in modern web and app development. Volunteers are highly welcome... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'mssc-16112013-clean-code-developer-your-workspace.html'; this.page.identifier = '9271ed1c97_idmssc-16112013-clean-code-developer-your-workspace'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "multiselect-komponente.html": {
    "href": "multiselect-komponente.html",
    "title": "MultiSelect-Komponente - Get Blogged by JoKi",
    "summary": "MultiSelect-Komponente Copy Markdown View Markdown GitHub Markdown Source MultiSelect-Komponente Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'multiselect-komponente'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'multiselect-komponente'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'MultiSelect-Komponente') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-03-05 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Heute mal einen technischen Eintrag zu Visual FoxPro und komponentenbasierter Programmierung. Die Selektion von mehreren Elementen in einem Control wie etwa dem Grid (Datenrasterelement bzw. DataGrid) oder einem TreeView-Control ist sicherlich praktisch und generell interessant. Daher meine Idee eine passable Komponente für Acodey zu schreiben. Warum eine Komponente? Nun, zum einen ist in Acodey alles in kleine Bauteile aufgeteilt und wird dann wieder in Komposits zusammengesteckt und zum anderen habe ich wenig Interesse eine zu starke Bindung mit dem Zielobjekt einzugehen. Daher lieber einen Baustein, der genau eine Aufgabe besitzt und diese entsprechend zu erfüllen weiß. Okay, als Basisklasse nehmen wir ein Label. Das hat den Vorteil, dass man beim Zusammenstecken von Bausteinen etwa in einen Container oder auf einer Form immer schön den Überblick behält und sieht - im wörtlichen Sinne - welche Funktionalität das produzierte Gebilde besitzt. Gut, damit haben wir klassentechnisch die Grundlage geschaffen. Wie sieht's nun mit der Schnittstelle aus? Klar, irgendwie muss ein Wert - in welcher Form auch immer - an die und von der MultiSelect-Komponente übergeben werden können. Da der Datentyp unterschiedlich ausfallen kann, nehmen wir uValue (u wie undefined. Nein, kein v wie variant, weil v für views in VFP genutzt wird). Die dazu gehörenden Get/Set-Methoden werden durch Aktivierung der Access/Assign-Methoden reguliert. Jaja, nix tippen, klicken ist angesagt, und die Methoden stehen zur Verfügung. Als nächstes brauchen wir ein Zielobjekt, auf das die Multiselektion wirken kann - fein, neue Eigenschaft oControl anlegen und zur Laufzeit eine Referenz auf das Zielobjekt ablegen. Dies ermöglicht es uns, dass wir innerhalb der Klasse mittels This.oControl auf das Objekt wirken können und unsere Kapselung besser wird. Auch lässt sich dann besser mit dem Scope arbeiten. Gut, dann je eine Methode zum Setzen und Lesen eines Wertes, welche direkt auf This.oControl wirken und daher in unserer Klassendefinition als abstrakt anzusehen sind. Die Implementierung erfolgt in einer Ableitung bzw. Instanz der Klasse und geht auf Spezialitäten des Zielobjektes ein - schliesslich hat ein Grid ein anderes Interface als beispielsweise ein TreeView Control. Wenn das fehlerfrei funktioniert, kümmern wir uns um den nächsten Schritt. Die Verarbeitung von mehreren Werten kann unterschiedlich erfolgen. Als Ausgangsmenge bieten sich auch unterschiedlich Quellen - CSV-Liste, Cursor bzw. Tabelle oder eine Collection - an. Für jeden Quelltyp erstellen wir geschützte Get/Set-Methoden, die sich zum einen um das Zerlegen und Zusammensetzen der Werte kümmern, und zum anderen in einer Schleife die Methode für einen Wert auslösen. Das Konzept der Interface-Überladung ist in VFP ein wenig anders zu handhaben, aber dennoch sind wir in der Lage es zu nutzen. Jetzt erstellen uns zwei öffentliche Methoden, die es uns gestatten die Menge der Werte entgegen zu nehmen bzw. zu liefern. Ich verwende This.SetValues(tuValues As Variant) zum Setzen der Werte und entsprechend This.GetValues zum Abrufen. In den Methoden erfolgt mittels Fallunterscheidung nach Parametertyp der Aufruf der jeweils zuständigen, internen Methode. Also, wenn man eine CSV-Liste in SetValues() gibt, dann erfolgt die Weitergabe an SetValuesCsv(), usw. Für die Bestimmung des Ausgabeformats müssen wir unserer Komponente noch eine weitere, öffentliche Eigenschaft - nValueType als Enumeration für die möglichen Rückgabewerte - und eine Methode GetCount() spendieren. This.GetValues entscheidet dann intern auf Grund dieser Eigenschaft, welche Methode zur Assimilation der Menge an Werte (die Anzahl liefert und GetCount) zu verwenden ist, und liefert das gewünschte Resultat als CSV-Liste, Cursor, Collection, etc. zurück. Damit hätten wir das Grundgerüst für weitere Spezialisierungen. Dabei sind wir von den Eigenheiten des Zielobjekts abhängig. Ich nehme an, dass es brauchbar sein dürfte, eine Ableitung pro Grundtyp an Objekten anzulegen. Danach sind lediglich 3 Methoden zu implementieren: SetValue, GetValue und GetCount In den drei Methoden wird auf das Interface des Objekts eingegangen; den Rest haben wir bereits einheitlich in der ParentClass. Und fertig ist ein universeller Baustein für MultiSelektion in Visual FoxPro. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'multiselect-komponente.html'; this.page.identifier = '9271ed1c97_idmultiselect-komponente'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "my-first-cyclone.html": {
    "href": "my-first-cyclone.html",
    "title": "My First Cyclone - Get Blogged by JoKi",
    "summary": "My First Cyclone Copy Markdown View Markdown GitHub Markdown Source My First Cyclone Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'my-first-cyclone'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'my-first-cyclone'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'My First Cyclone') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2007-02-23 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Februar ist die Zeit der Wirbelstürme; im Indischen Ozean Zyklone genannt. Gestern war bereits Warnstufe 1 ausgerufen, jedoch sind wir davon ausgegangen, dass es noch bis heute Abend dauern bis es interessant werde dürfte. Nun, unsere Planung für heute sah meinen ersten Arbeitstag auf Mauritius vor, aber bereits um 7:30 Uhr kam Dass an, und meinte, dass wir inzwischen Warnstufe 2 haben und es nicht empfehlenswert sei auf die Arbeit zu gehen. Gerade in Hinblick auf Warnstufe 3 ist die Empfehlung ASAP nach Hause zu gehen. Was sich als schwierig herausstellen könnte. Naja, lange Rede, kurzer Sinn (oder so...): Bleibe ich eben zuhause und quäle den Laptop von hier aus. Ist ja nicht so, dass ich nichts zu tun hätte... Im Gegenteil, es sind noch einige Programmierarbeiten im laufenden Projekt zu realisieren. Nutzen wir die sturmreiche Zeit für was Sinnvolles. Achja, hier noch der Link auf Mauritius Meteorological Services. Dort sind die Typen von Wetterlagen und die Klassifizierung von Zyklonen beschrieben. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'my-first-cyclone.html'; this.page.identifier = '9271ed1c97_idmy-first-cyclone'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "nachlese-vfp-stammtisch-speyer-im-april.html": {
    "href": "nachlese-vfp-stammtisch-speyer-im-april.html",
    "title": "Nachlese: VFP Stammtisch Speyer im April - Get Blogged by JoKi",
    "summary": "Nachlese: VFP Stammtisch Speyer im April Copy Markdown View Markdown GitHub Markdown Source Nachlese: VFP Stammtisch Speyer im April Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nachlese-vfp-stammtisch-speyer-im-april'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nachlese-vfp-stammtisch-speyer-im-april'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Nachlese: VFP Stammtisch Speyer im April') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-20 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hallo zusammen, vielen herzlichen Dank für eure aktive Teilnahme bei der Gestaltung des heutigen Stammtisch. Ich bin richtig begeistert, dass wir es geschafft haben, den Speyerer Fuchs-Treff wieder richtig gut in Schwung zu bringen - klasse! Danke. Also, wenn ich mich nicht verzählt haben sollte, dann waren wir lockere 16, 17 Personen (mich eingeschlossen) - Der Hammer! Vor allem, wenn man bedenkt, dass fünf weitere Personen im Vorfeld abgesagt haben. Was sicherlich an dem starken Thema des Abends - EventBinding in VFP - lag. Mein Dank gilt Michael für seinen freiwilligen Einsatz. Entsprechend geht's auch weiter voran im Bereich der Stammtisch internen Kommunikation. Ich hab' gerade von Olli die aktuellen Informationen für die Y! Group erhalten. Sobald ich damit ein wenig firm bin, schick' ich die Infos ebenfalls per Mail an euch raus. Seid gespannt Okay, da im Laufe des Abends über diverse Themen - unter anderem auch die tolle Unterstützung von O'Reilly mit den Büchern zu Subversion - gesprochen wurde, möchte ich hier noch kurz die wichtigsten Infoquellen zusammentragen: Subversion - https://subversion.tigris.org RapidSVN - https://rapidsvn.tigris.org TortoiseSVN - https://tortoisesvn.tigris.org SourceOffSite Client - https://www.sourcegear.com/sos/downloads.html DbDesigner 4 - https://www.fabforce.net Die Visual FoxPro Konferenz in Prag findet vom 21. bis 23. Juni statt. Weitere Informationen, wie Preise, Registrierung und Sessionplan könnt ihr auf der Konferenzsite - https://www.daquas.cz/fox/devcon2005an - lesen. Achja, bei UT gibt's Details zur Konferenz 2004 - https://www.utcoverage.com/Prague/2004/ Es wäre extremst cool, wenn wir 'ne Fox-Tour organisiert bekämen. Puuh, wie geht's weiter... Hm, ich würde sagen, dass ich die beiden Entscheidungen - Thema für Mai und häufigere Treffen - als eigenständige Umfragen an euch versenden werde. Vielleicht können wir dazu auch die Y! Group verwenden (mal schauen...). Das ist Community - und die Füchse haben's richtig gut drauf! Bis denne, JoKi PS: Für das Treffen im Mai sollte ich vllt. schon den zweiten Raum vormerken lassen... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'nachlese-vfp-stammtisch-speyer-im-april.html'; this.page.identifier = '9271ed1c97_idnachlese-vfp-stammtisch-speyer-im-april'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "nachlese-zur-community-devcon-2005.html": {
    "href": "nachlese-zur-community-devcon-2005.html",
    "title": "Nachlese zur Community Devcon 2005 - Get Blogged by JoKi",
    "summary": "Nachlese zur Community Devcon 2005 Copy Markdown View Markdown GitHub Markdown Source Nachlese zur Community Devcon 2005 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nachlese-zur-community-devcon-2005'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nachlese-zur-community-devcon-2005'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Nachlese zur Community Devcon 2005') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-13 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Cool, heute kam die CD von der Community Devcon 2005 vom Februar in Mörfelden. So ziemlich alles ist drauf gepackt - Klasse. Gerade die Slides und die Zusatzinformationen der CodeZone sind sehr informativ und garantiert nochmal lesenswert. Achja, da ich es gerade in den Unterlagen der CD sehe, die Microsoft HellHounds sind Deutscher Meister im Robot-Fußball geworden. Gratulation meinerseits. So, nun aber mal weiter an den Vorbereitungen für den nächsten VFP Stammtisch in Speyer arbeiten - Wir sehen uns... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'nachlese-zur-community-devcon-2005.html'; this.page.identifier = '9271ed1c97_idnachlese-zur-community-devcon-2005'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "net-for-visual-foxpro-developers.html": {
    "href": "net-for-visual-foxpro-developers.html",
    "title": ".NET for Visual FoxPro Developers - Get Blogged by JoKi",
    "summary": ".NET for Visual FoxPro Developers Copy Markdown View Markdown GitHub Markdown Source .NET for Visual FoxPro Developers Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'net-for-visual-foxpro-developers'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'net-for-visual-foxpro-developers'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '.NET for Visual FoxPro Developers') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-06 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nach einiger Zeit endlich die erste Rezension zu einem Buchtitel. Ich habe die Veröffentlichung auf Amazon realisiert, daher biete ich euch hier einfach den Verweis auf die Seite dort an: https://www.amazon.de/exec/obidos/ASIN/1930919301 Zumal Amazon in den Bestimmungen zu Rezensionen explizit auf die alleinige Nutzung hinweist. Und wir wollen uns ja keinen 'Ärger' kassieren. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'net-for-visual-foxpro-developers.html'; this.page.identifier = '9271ed1c97_idnet-for-visual-foxpro-developers'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "net-per-com-nutzen.html": {
    "href": "net-per-com-nutzen.html",
    "title": ".NET per COM nutzen - Get Blogged by JoKi",
    "summary": ".NET per COM nutzen Copy Markdown View Markdown GitHub Markdown Source .NET per COM nutzen Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'net-per-com-nutzen'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'net-per-com-nutzen'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '.NET per COM nutzen') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-19 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Heute war wieder einmal so ein Fall der Fälle bei denen ich es geniesse, dass ich inzwischen mit mehr Programmiersprachen als mit Visual FoxPro zu tun habe. Bedingt durch ein Kundenprojekt meines Kollegen ergab sich die Aufgabenstellung, dass Funktionalitäten in einer DLL - vermutlich eine .NET Library - in die bestehende VFP-Anwendung zu integrieren sind. Nun, prinzipiell ist das eine einfache Sache. COM-Interface instanziieren und wohlfühlen. Beispielcode vorhanden... Leider stand meinem Kollegen nur Beispielcode in Visual Basic (.NET) zur Verfügung. Okay, also, dann schauen wir uns mal die DLL an. Zusätzlich zur reinen Dynamic-Link Library liegt im Verzeichnis ebenfalls eine .TLB (Type Library) Datei vor. Sehr gut, diese können wir problemlos im Object Browser von VFP einlesen und haben damit Informationen zu den Innereien der DLL an der Hand. Gut, zwei Klassen, einige Methoden und 4 Interfaces. Sieht doch schon mal gut aus. Nur... wie erhalten wir eine Objektreferenz auf diese Klassen? Probieren wir es zuerst mal mit dem Offensichtlichen: oTest = CreateObject(\"DllName.KlassenName\") Leider Fehlschlag, der Moniker steht nicht zur Verfügung. Windows Registry to the rescue! Bewaffnet mit dem Klassennamen öffnen wir den Registrierungseditor und suchen ausgehend von der Wurzel nach der Klasse. Und der erste Treffer liefert uns auch dankenswerterweise die benötigte ProgID des COM-Servers. Wir sehen sofort, dass die Komponente leider eine eher unübliche ProgID besitzt, diese sieht nämlich sehr verdächtig nach .NET Namespacekonvention mit Firma.Produkt.Klasse aus. Okay, lassen wir uns nicht weiter dadurch von der Arbeit abhalten. Der Aufruf im Command Window von oTest = CreateObject(\"Firma.Produkt.KlassenName\") führt direkt zum Erfolg - Auch wenn wir eine merkliche Verzögerung bzw. Zwangspause zum Laden des .NET Frameworks hinter den Kulissen feststellen können. Egal, Hauptsache wir haben eine gültige Referenz auf das Objekt und können weiterarbeiten. Leider greift das gewohnte Intellisense noch nicht, aber dafür werden potentiell auch noch eine Lösung finden. Windows Form in VFP anzeigen Die erste Überraschung folgt auch so gleich bei der weiteren Verwendung der Objektreferenz. Laut Object Model und Aufgabenstellung müssen wir eine Suchabfrage absetzen und dann auf ein entsprechendes Event reagieren. Gut, wir schauen uns die zu spezifizierenden Datentypen der Methode an und lösen die Funktion aus. Nach kurzer Pause taucht eine Windows Form mit den Trefferdatensätzen in einem DataGrid auf - coole Sache. Und da erzählt uns Ken Levy, dass das nicht möglich sei... \uD83D\uDE09 - Gut, ich hab' dazu ebenfalls schon einen generischen COM-Server für unsere Testzwecke geschrieben. Weiter im Kontext, also einen Treffer selektieren und dann? Ja, dann kommt das Event ins Spiel. Auf .NET / COM Events in VFP reagieren Um auf Ereignisse von COM-Servern im Allgemeinen in Visual FoxPro reagieren zu können, ist die Funktion BindEvent() leider nicht brauchbar. Diese kann lediglich eine Bindung auf foxproeigene Objekte und auf Windows Messages erstellen. In Zusammenarbeit mit COM-Servern müssen wir eine Eventklasse des Interface des Servers erstellen - dort können wir unseren Code eintragen -, und dann verheiraten wir die beiden Objektreferenzen per EventHandler() Funktion miteinander. Das hört sich im ersten Moment wesentlich schlimmer an, als es dann tatsächlich ist. Alles eine Frage der Übung. Die Eventklasse des Interface erstellen wir uns per Drag&Drop über den Object Browser. Dazu öffnen wir eine leere PRG Datei und droppen dann einfach vom Object Browser das benötigte Interface unseres COM-Servers hinein. Flupp erscheint der komplette Code, den wir brauchen. Zusätzlich passen wir nun noch den Klassennamen und den Verweis der Implements-Anweisung auf die ProgID an und können dann in den einzelnen 'Event-Methoden' unseren Code eintragen. Wichtig, niemals die Signatur der Klasse ändern, sonst klappt die Kopplung mit dem COM-Server nicht mehr. Hier mal exemplarisch wie das aussehen könnte: ` oCom = CreateObject(\"Firma.Produkt.KlassenName\") oEvents = CreateObject(\"KlassenNameEvents\") EventHandler(oCom, oEvents) Define Class KlassenNameEvents As Session OlePublic Implements IKlassenEvents In \"Firma.Produkt.KlassenName\" ; Procedure IKlassenEvents_OnSelection& #40;Primary As Integer) add your code here EndDefine ` So in etwa dürfte das auch bei euch aussehen. Es besteht übrigens kein Unterschied, ob es sich um einen 'klassischen' COM-Server oder einen .NET-basierten handelt. COM ist COM und funktioniert gleichermaßen. Problem erkannt, Problem gelöst Nach diesem kleinen Exkurs in die Welt der COM-Server und des .NET-Frameworks fühlt sich mein Kollege wieder in der VFP-Programmierung heimisch und die Integration ist bereits im Teststadium, da alles wie gewünscht gelaufen ist. Bis denne, JoKi PS: Die ProgID eines COM-Servers hat übrigens erstmal nichts mit dem .NET Namespace zu tun und sollte auch einer anderen Konvention (Library.Klasse) folgen. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'net-per-com-nutzen.html'; this.page.identifier = '9271ed1c97_idnet-per-com-nutzen'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "net-usertreffen-kaiserslautern.html": {
    "href": "net-usertreffen-kaiserslautern.html",
    "title": ".NET Usertreffen Kaiserslautern - Get Blogged by JoKi",
    "summary": ".NET Usertreffen Kaiserslautern Copy Markdown View Markdown GitHub Markdown Source .NET Usertreffen Kaiserslautern Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'net-usertreffen-kaiserslautern'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'net-usertreffen-kaiserslautern'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '.NET Usertreffen Kaiserslautern') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-05 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); \"Willkommen im Kino der .NET Usergroup Kaiserslautern...\" So ähnlich konnte man den gestrigen Abend beginnen. Nach einiger Zeit fand ich endlich mal wieder die Muße und die Möglichkeit bei Usertreffen der hiesigen .NET-Coder teilzunehmen. Nunja, eigentlich mehr die Pflicht daran teilnehmen zu müssen, denn schliesslich sollte ich ja das Thema des Abends gestalten: Microsoft SQL Server 2005 - Neue Features und Verbesserungen. Nach ein paar einleitenden Worten von Golo über die aktuelle Situation der UG, den Ereignissen der letzten vier Wochen - insbesondere des Technical Summits - und der Vorstellung der ersten Bücher der UG-Bibliothek steigen wir kurz nach 20:00 ins Thema SQL Server ein. Mein armes Notebook... snief Nach der Installation letzter Woche wurde es dann gestern 'ernst'. Well, nach anfänglichen Problemen mit der Diskussion welches Ausgabegerät (TFT oder Beamer) denn nun das primäre Display mit welcher Auflösung erhält, konnte es losgehen. Nunja, SQL Server 2005, SQL Server 2000 und dann noch das SQL Server Management Studio gleichzeitig laufen lassen, könnte doch die ein oder andere Ressource benötigen. Nunja, wir hatten ja Zeit und daher lief der Abend wesentlich gemütlicher als geplant - hehehe, so kann man auch die Zeit überbrücken... Das Feedback an Fragen war wirklich toll. Ich hatte auf alle Fälle wieder einmal viel Spass auf dem Usertreffen und freue mich bereits auf den nächsten Termin. Diesmal beim Stuttgarter VFP Stammtisch und dabei geht's um Additive Datenbanken zu Visual FoxPro und nicht nur SQL Server 2005 \uD83D\uDE09 Weitere Infos gibt's in der Nachlese von Golo zum gestrigen Abend. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'net-usertreffen-kaiserslautern.html'; this.page.identifier = '9271ed1c97_idnet-usertreffen-kaiserslautern'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "neue-optik-fr-den-hintergrund.html": {
    "href": "neue-optik-fr-den-hintergrund.html",
    "title": "Neue Optik für den Hintergrund - Get Blogged by JoKi",
    "summary": "Neue Optik für den Hintergrund Copy Markdown View Markdown GitHub Markdown Source Neue Optik für den Hintergrund Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'neue-optik-fr-den-hintergrund'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'neue-optik-fr-den-hintergrund'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Neue Optik für den Hintergrund') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-02-01 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Die bei Windows XP und anderen Windows Betriebssystemen mitgelieferten Hintergrundbilder, neudt. Wallpapers, haben bereits ihren Charme. Jedoch ist der bekannte Anblick über die Jahre hinweg ein wenig ausgelutscht. Also, was tun? Nun, in den Weiten des Internets gibt es wie immer unendliche Möglichkeiten und auf der Suche nach Abwechselung habe ich mir mal diverse Websites für Wallpaper angeschaut. Zuerst ein wenig in der Erinnerung gekramt und bei deviantART reingeschaut. Dort hatte ich mir zuletzt vor zwei, drei Jahren mal wirklich coole Bilder für meine Linux-Maschinen gezogen. Absolut empfehlenswerte Seite. Selbstverständlich dürfen in dieser Auswahl die Schlagseiten und Cover der Fachzeitschrift c't des Heise Verlag nicht fehlen. Weiter geht's zu den diversen Themes-Sites. Hier mal 'ne kleine Auflistung dazu: https://www.deviantart.com https://kde.themes.org Achja, auf den meisten Ressourcen findet man neben Hintergrundbildern viele weitere Möglichkeiten zur Verschönerung der heimischen Optik des Rechners - Themes, Sounds, Iconsets, etc... Welche Sites besucht ihr unregelmäßig, wenn ihr auf der Suche nach Hintergrundbildern seid? Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'neue-optik-fr-den-hintergrund.html'; this.page.identifier = '9271ed1c97_idneue-optik-fr-den-hintergrund'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "neues-release-585.html": {
    "href": "neues-release-585.html",
    "title": "Neues Release #585 - Get Blogged by JoKi",
    "summary": "Neues Release #585 Copy Markdown View Markdown GitHub Markdown Source Neues Release #585 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'neues-release-585'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'neues-release-585'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Neues Release #585') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-11 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Yes, endlich mal wieder ein neues Release der Active FoxPro Pages - mein Tool der Wahl zur Entwicklung meiner Websites. Was soll ich noch weiter schreiben? Lest selbst auf https://www.afpages.com/?lang=de&cat=news Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'neues-release-585.html'; this.page.identifier = '9271ed1c97_idneues-release-585'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "new-face--new-service.html": {
    "href": "new-face--new-service.html",
    "title": "New face, new service - Get Blogged by JoKi",
    "summary": "New face, new service Copy Markdown View Markdown GitHub Markdown Source New face, new service Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-face--new-service'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-face--new-service'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'New face, new service') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-01-25 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ahhhhh, heute hat definitiv eine zweite Bescherung stattgefunden. Warum? - Ganz einfach, ich tippere hier gerade ein wenig am Blog und betrachte die neue Hardware auf meinem Schreibtisch. Hm, okay, Hardware alleine ist nicht ausreichend... Nach der Bestellung am Dienstag mittag und der prompten Zustellung heute Abend kann ich mich ein wenig ausgiebiger an den Leistungsdaten, dem Gehäuseaufbau und der Software (hüstel) meines zappelfrischen Notebooks ergötzen. Hier mal ein kurzer Überblick zu den Leistungsdaten: Asus A6KM AMD Turion64 MT34 1 GB RAM 100 GB Harddisk 8x Double Layer DVD-Brenner und die üblichen Schnittstellen... Der temporäre Umgang mit dem Notebook von Thomas hat schon ziemlich Spass gemacht. Das A6KM ist das Nachfolgermodell zu seinem. Und ich muss sagen, Asus hat hier nochmal zulegen können und ein paar schnuckelige Detailverbesserungen eingestreut. Und anderem ist es nun problemlos möglich, die Wiedergabe von AudioCDs bei ausgeschaltetem Laptop über die Extratasten zu handhaben. Vorher musste man die Klappe des Displays öffnen. Apropos Display, das ist erste Manko, das ich in der Kürze der Zeit feststellen durfte... Wie kann man eigentlich ein nicht-entspiegeltes Display verbauen? Sehr irritativ, das man sich ständig beim Arbeiten selbst anschaut. Aber es soll ja Zeitgenossen geben, die diese Situation als Feature deklarieren würden. Ich werd' dennoch mal zusehen, dass ich in irgendeiner Weise einen Matt-Effekt hinbekomme. Gerade die Reflexion von Lichtquellen ist auf Dauer äußerst störend. Als vorinstallierte Software ist bereits Windows XP Professional Service Pack 2 am Werkeln... Leider die 32Bit-Ausführung. Nunja, ist ja auch so sinnvoll auf einem 64Bit Notebook die abgespeckte Variante laufen zu lassen. Irgendwie traurig, aber hey was soll's... Ich hab' eh noch ein paar Anpassungen an der Partitionierung der Festplatte und damit auch den Betriebssystemen durchzuführen. Stellt sich nur die Frage, ob das vorinstallierte System überhaupt auf der Kiste bleibt oder nicht. Wir werden sehen. Meine gegenwärtige Planung für die Aufteilung der Platte sieht folgendermaßen aus: 20 GB Windows Server 2003 Enterprise Edition 64Bit 10 GB Windows Vista 64Bit 20 GB Debian GNU/Linux (evtl. auch Ubuntu) 50 GB Datenablage für den gemeinsamen Zugriff aus allen Systemen Die 10 GB für Vista sind Spielwiese zum Experimentieren mit dem kommenden Operating System aus Redmond. Meine bisherigen Beta-Experiences waren gemischt. Einige neue Features sind mal richtig obercool, während andere Änderungen absoluter Bullshit sind und man sich fragen könnte, auf welchem Trip die betreffende Abteilung unterwegs war... \uD83D\uDE42 Ich bin mal auf die automatische Hardwareerkennung und Treiberversorgung in den drei Systemen gespannt. Wie seht ihr diese Aufteilung? Hab' ich was übersehen? Wie partitioniert ihr eure Systeme? Damit dürfte ich eine optimale mobile Arbeitstation besitzen. So zumindest meine Hoffnung... \uD83E\uDD2A - Wir werden sehen, ob und wie das Ganze sich im Echteinsatz verhalten wird. Problemberichte wird's eventuell geben. Auch in Hinsicht auf die Erkenntnisse mit der Vista Beta. Stay tuned! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'new-face--new-service.html'; this.page.identifier = '9271ed1c97_idnew-face--new-service'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "new-infopath-template-for-blogging.html": {
    "href": "new-infopath-template-for-blogging.html",
    "title": "New InfoPath template for blogging - Get Blogged by JoKi",
    "summary": "New InfoPath template for blogging Copy Markdown View Markdown GitHub Markdown Source New InfoPath template for blogging Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-infopath-template-for-blogging'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-infopath-template-for-blogging'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'New InfoPath template for blogging') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-22 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Today, I finally switched my InfoPath template from SimpleBlogger to CSBlogger. Now I'm again able to write my postings to the Foxite weblog offline in InfoPath and submit the content anytime later. Editing articles and reading comments is possible as well. Personally, I'd like this kind of front-end to web sites. The InfoPath template is more comfortable than the web client, and therefore I'm able to write my entries in chunks even while not connected to the internet. So, if you're interested in CSBlogger take a look at the URL http://gqwu.members.winisp.net/blogeditor/ The latest version 1.1 works fine with the Community Server here at Foxite. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'new-infopath-template-for-blogging.html'; this.page.identifier = '9271ed1c97_idnew-infopath-template-for-blogging'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "new-msdn-forums.html": {
    "href": "new-msdn-forums.html",
    "title": "New MSDN Forums - Get Blogged by JoKi",
    "summary": "New MSDN Forums Copy Markdown View Markdown GitHub Markdown Source New MSDN Forums Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-msdn-forums'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-msdn-forums'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'New MSDN Forums') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-08 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wow... ...kam gerade per RSS rein: Those of you reading this blog for a while know that I'm very committed to improving our feedback loop here at Microsoft. In that spirit, I just wanted to give a congrats to the team that has just launched our new MSDN forums. Currently in use for VS2005, I hope that these get picked up by other teams over time (and yes, we're looking into it for Fox). The ease of a web interface, RSS feeds for tracking, IM or Email when your question gets answered, integration with the VS2005 shell, easy searching to lok for an answer to your question (something that I always found missing with newsgroups). I'm really excited by the possibilities... Ich bin mal gespannt, wie darauf reagiert werden wird. Jedenfalls habe ich nun die Bürde den ersten Thread - Very nice! - https://forums.microsoft.com/msdn/ShowPost.aspx?PostID=899 - in der Visual FoxPro Sektion - https://forums.microsoft.com/msdn/ShowForum.aspx?ForumID=60 - gestartet zu haben... Let's thread together! MSDN Forum - Visual FoxPro dFPUG myCSharp.de .o( ... was so manche Erinnerung weckt hüstel ) if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'new-msdn-forums.html'; this.page.identifier = '9271ed1c97_idnew-msdn-forums'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "new-pluralsight-app-for-android-will-bring-variable-speed.html": {
    "href": "new-pluralsight-app-for-android-will-bring-variable-speed.html",
    "title": "New Pluralsight app for Android will bring variable speed - Get Blogged by JoKi",
    "summary": "New Pluralsight app for Android will bring variable speed Copy Markdown View Markdown GitHub Markdown Source New Pluralsight app for Android will bring variable speed Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-pluralsight-app-for-android-will-bring-variable-speed'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-pluralsight-app-for-android-will-bring-variable-speed'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'New Pluralsight app for Android will bring variable speed') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-06-18 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Usually, it is very interesting to compare the 'same' software applications and apps between various platforms, and the Pluralsight offline players are an outstanding example to have a closer look at. The original Silverlight desktop offline player, the iOS app of Pluralsight and the new HTML5 online player all have variable playback speed. Just the Android app doesn't. But not for long. A couple of days Pluralsight finally announced that their development department found a reliable solution to provide flexible speeds. For the brave ones among us, please have a look at the public beta they are offering on their blog article. Please be aware of, that using any Pluralsight offline player requires to have a Plus subscription. Otherwise, you cannot use the application after all. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'new-pluralsight-app-for-android-will-bring-variable-speed.html'; this.page.identifier = '9271ed1c97_idnew-pluralsight-app-for-android-will-bring-variable-speed'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "new-years-resolution-2012.html": {
    "href": "new-years-resolution-2012.html",
    "title": "New Year's resolution 2012 - Get Blogged by JoKi",
    "summary": "New Year's resolution 2012 Copy Markdown View Markdown GitHub Markdown Source New Year's resolution 2012 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-years-resolution-2012'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'new-years-resolution-2012'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'New Year's resolution 2012') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-01-03 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Same procedure as every year... Hundreds of thousands of people have their annual new year's resolution to begin the new year. And so am I. My resolution for 2012: Writing more blog articles (again). Actually, it's quite difficult to find to proper time and space to write up an article for any kind of blog, newspaper or magazine. Especially, when you are very busy with daily work and fulfilling customers demands with very tight schedules. But seriously, I'll try to keep it up with at least one or two articles per month during 2012. There are quite some good topics to write about in the queue. Cheers, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'new-years-resolution-2012.html'; this.page.identifier = '9271ed1c97_idnew-years-resolution-2012'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "nginx-reverse-proxy.html": {
    "href": "nginx-reverse-proxy.html",
    "title": "Using nginx as reverse proxy - Get Blogged by JoKi",
    "summary": "Using nginx as reverse proxy Copy Markdown View Markdown GitHub Markdown Source Using nginx as reverse proxy Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nginx-reverse-proxy'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nginx-reverse-proxy'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Using nginx as reverse proxy') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-01-08 - Filed under LinuxDevelopment (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nginx (read: engine-x) has versatile options to set up web sites and more advanced configurations. This article explains briefly how to set up nginx as a reverse proxy to a web site in an internal network. NGINX is a free, open-source, high-performance HTTP server and reverse proxy, as well as an IMAP/POP3 proxy server. Source: https://www.nginx.com/resources/wiki/ The scenario I have a web site running on a system in an internal network. This could be either a full-fledged Windows/Linux server or an IoT device running on a single board computer (SBC), like i.e. a Raspberry Pi, an Arduino, ESP8266 chipset. Now, I want to enable access from the Internet to that internal server using nginx. Setting up nginx In order to set up the solution you need to have a public facing web server on the Internet. Most probably it already runs nginx to serve your web site or blogging software. I'm running a root server on Debian/GNU Linux and nginx is already installed. You can check your own system quickly like so for any running process: $ ps fax | grep nginx Or if you prefer a bit more details like so: $ sudo service nginx status ● nginx.service - A high performance web server and a reverse proxy server Loaded: loaded (/lib/systemd/system/nginx.service; enabled) Active: active (running) since Do 2019-01-03 03:28:11 CET; 4 days ago Docs: man:nginx(8) Process: 29505 ExecStop=/sbin/start-stop-daemon --quiet --stop --retry QUIT/5 --pidfile /run/nginx.pid (code=exited, status=0/SUCCESS) Process: 29537 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Process: 29535 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS) Main PID: 29539 (nginx) CGroup: /system.slice/nginx.service ├─29539 nginx: master process /usr/sbin/nginx -g daemon on; master_process on; ├─29540 nginx: worker process ├─29541 nginx: worker process ├─29542 nginx: worker process └─29543 nginx: worker process In case that nginx is not even installed on your system you could look up the package information like so: $ apt search ^nginx And install the web server using apt-get like so: $ sudo apt-get install nginx-full Which will then install nginx web/proxy server and all its dependencies on your server. Configuring nginx as reverse proxy Now, we have an operational installation of nginx on our Internet-facing system. We are going to create a new configuration file that defines the necessary proxy information to access our service on the internal network. First create a new file below nginx configuration folder using your preferred text editor. $ cd /etc/nginx/sites-available/ $ sudo nano raspberry The file name should be relevant to either the kind of services or the system that you are going to shield using nginx as proxy. Next, write the following server definition into your configuration file. Of course, you would adjust the server name and the IP address according to your environment: server { listen 80; listen [::]:80; server_name raspberry.kirstaetter.name; server_tokens off; location / { proxy_set_header X-Real-IP $remote_addr; proxy_pass 10.0.240.3; } } That is the minimal configuration you would have to specify in order to run nginx as a reverse proxy to a system on your internal network. The given IP address needs to be accessible from your public web server, i.e. using a VPN infrastructure based on OpenVPN. After saving and closing the new nginx configuration it is time to enable and check the syntax for any errors. To enable an available configuration you need to either place it or link it into the folder sites-enabled of nginx. $ cd ../sites-enabled $ sudo ln -s /etc/nginx/sites-available/raspberry raspberry Now, to avoid any unexpected shutdowns or better said launching issues you should always run a configuration test before restarting the nginx service. This can be done quickly using the following command: $ sudo service nginx configtest [ ok ] Testing nginx configuration:. Should your configuration file have any unknown directives and errors the output of configtest looks like this: $ sudo service nginx configtest [FAIL] Testing nginx configuration: failed! You will find more details about the nature of the problem and the line number in the error log file below /var/log, i.e. here: $ sudo cat /var/log/nginx/error.log 2019/01/07 13:50:07 [emerg] 21662#21662: unknown directive \"server_?name\" in /etc/nginx/sites-enabled/raspberry:5 Only when all problems have been resolved and you have a positive response from the configtest you should restart the nginx service. $ sudo service nginx restart Resolve a domain name The above described sample is very basic, and sometimes it might be necessary to avoid using an IP address for internal service. Luckily, this can configured using the resolver directive in an nginx configuration file like so: server { listen 80; listen [::]:80; server_name raspberry.kirstaetter.name; server_tokens off; resolver 127.0.0.1; location / { proxy_set_header X-Real-IP $remote_addr; proxy_pass rasp01.local; } } The change in our configuration file now assumes that I have a DNS server running on the local machine which knows how to handle and resolve the specified domain name rasp01.local. Again, this article covers the basics of reverse proxying using nginx only. There are more interesting scenario like setting your own DNS server on the internal network to provide public access to an internal resource. Perhaps, you might want to proxy an existing service with your own custom domain, in case that the service provide does not offer this option. Using a public DNS server like Cloudflare's 1.1.1.1, Google Public DNS (8.8.8.8), or OpenDNS as resolver should give you some ideas. Provide secure access using SSL Let's take the following scenario into consideration. Your internal resource might not be configurable with an SSL certificate but you would like to enable HTTPS protocol communication from the Internet. Setting up nginx with an SSL certificate is well-documented and to combine this with the above described proxy features is a breeze to achieve. Following you will get a more complete configuration file based on the previous example, now SSL-enabled using a Let's Encrypt certificate. server { listen 80; listen [::]:80; listen 443 ssl http2; listen [::]:443 ssl http2; server_name raspberry.kirstaetter.name; server_tokens off; server_name_in_redirect off; client_max_body_size 50m; ssl on; ssl_certificate /etc/letsencrypt/live/raspberry.kirstaetter.name/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/raspberry.kirstaetter.name/privkey.pem; # modern configuration. tweak to your needs. ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256'; ssl_prefer_server_ciphers on; # HTTP headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header X-XSS-Protection \"1; mode=block\"; add_header Referrer-Policy no-referrer-when-downgrade; root /var/www/raspberry; access_log /var/log/nginx/raspberry.kirstaetter.name.access_log gzip; error_log /var/log/nginx/raspberry.kirstaetter.name.error_log info; resolver 127.0.0.1; location / { proxy_set_header X-Real-IP $remote_addr; proxy_pass rasp01.local; } location ~ /.well-known { allow all; } } The specified SSL options in regards to protocols and ciphers are an arbitrary choice of mine. If you have suggestions on how to improve the SSL setup, please leave a comment below. Eventually the http2 directive might be an issue. Either check that you are using a recent version of nginx that has HTTP/2 support backed in or remove the value from the listen directive in the configuration file. Multiple proxies No problem with nginx. You can configure and run as many reverse proxies as would like to. Right now, I think I have three or four proxies running. Interestingly, one of them is an older set up based on Apache HTTP Server which I wrote about in a Using Apache HTTP as reverse proxy. Do you have any interesting use cases or active configurations of nginx as reverse proxy? If yes, please use the comment section below give me and other readers more details. Thanks! Image credit: Otto Norin if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'nginx-reverse-proxy.html'; this.page.identifier = '9271ed1c97_idnginx-reverse-proxy'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "nice-smeagol.html": {
    "href": "nice-smeagol.html",
    "title": "Nice Smeagol... - Get Blogged by JoKi",
    "summary": "Nice Smeagol... Copy Markdown View Markdown GitHub Markdown Source Nice Smeagol... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nice-smeagol'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nice-smeagol'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Nice Smeagol...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-23 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich glaube, dass der Release der Visual Studio Express Editionen bei Microsoft potentiell noch einige Kopfschmerzen bereiten könnte... Ich hab' mir mal den folgenden Blogeintrag - FLLs mit MS Visual C++ 2005 Express Edition - angesehen und finde das wiederum sehr spannend. Endlich kann der ambitionierte VFP-Entwickler sein angestaubtes Visual Studio 98 beiseite legen und auf die neuen Features der Express Editionen zugreifen. Bleibt halt die Frage wie lange 'vorerst' im Sinne von Microsoft dauert... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'nice-smeagol.html'; this.page.identifier = '9271ed1c97_idnice-smeagol'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "nichts-ist-unmglich.html": {
    "href": "nichts-ist-unmglich.html",
    "title": "Nichts ist unmöglich... - Get Blogged by JoKi",
    "summary": "Nichts ist unmöglich... Copy Markdown View Markdown GitHub Markdown Source Nichts ist unmöglich... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nichts-ist-unmglich'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nichts-ist-unmglich'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Nichts ist unmöglich...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-01-24 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); ...wenn man einer Idee Leben einhaucht und die Chance nutzt, diese zu realisieren. Ähnlich wie bei der Active FoxPro Pages 3.0 IntelliSense Extension (AFP3IE) schwirrte mir seit Monaten die Vorstellung durch den Kopf, dass man die Kommunikation zwischen Webserver und der AFP Core Engine auch über andere Wege als die ISAPI realisieren müsste / könnte. Mein ursprünglicher Ansatz dabei galt der Erstellung eines Dynamic Shared Object (DSO) für den Apache Webserver. Mangels Kenntnisse und Fähigkeiten in C++ habe ich diesen Ansatz jedoch frühzeitig auf Eis gelegt. Also... falls ein ambitionierter C++-Coder mit Wissen zur DSO-Erstellung hier lesen sollte, bitte melden. Well, parallel zur Entstehung der Idee hatte ich mir bereits einiges Wissen über das .NET Framework und insbesondere C# - egal ob System.Windows.Forms oder System.Web - angeeignet. Und darin lag auch der Schlüssel zur ersten Implementierung der Lösung. Glücklicherweise stehen mir als Regionalleiter der dFPUG und damit als Mitglied der INETA die Buchzuwendungen der letzten Quartale zur Verfügung. Und das Wälzen in eben dieser Literatur verlieh Flügel. Insbesondere die folgenden Bücher waren extremst lehrreich: C# Essentials Visual C# .NET Programmier-Rezepte Introducing ASP.NET 2.0 ASP.NET 2.0 - Step by step Durch das Verständnis des grundsätzlichen Aufbaus von ASP.NET und dessen Ablaufsequenz ergaben sich mehrere Ansätze. Dennoch habe ich mich in Bezug auf 2.0 für die Entwicklung eines HttpHandlers entschieden. Das Grundkonzept dabei ist ziemlich easy: Man nehme das Interface IHttpHandler und code ein paar Zeilen hinein, passe die Web.config noch ein wenig an und voilá: Funktioniert. Okay, ganz so easy war's dann doch wieder nicht. Denn... Schliesslich muss ja auch noch eine Kommunikation mit der AFP Core Engine erfolgen. Und das brachte mir doch einige schlaflose Nächte... Inzwischen ist auch hier Erfolg zu verzeichnen. Die Pflichtübung ist sozusagen erfüllt, jetzt beginnt die Kür. Was damit wiederum heißt: Verfeinerung, Stabilisierung und Dokumentation. Interessanterweise konnte ich die Umsetzung zuerst innerhalb der Programmierung einer Windows.Forms Anwendung realisieren. Was mich damit dann wiederum zu dem Schluß bringt, dass künftig die Verschmelzung der AFP Core Engine mit anderen Programmiersprachen kaum mehr Probleme bereiten dürfte. Nein, eher sogar im Gegenteil, da mit den jetzigen Erfahrungen die Implementierung von weiteren Clients sehr einfach von der Hand gehen dürfte. Bis denne, JoKi Achja, nur so als informativen Hinweis: Ich verwende kein COM-Interop, da wir bei der AFP-Entwicklung explizit auf das COM-Interface aus den unterschiedlichsten Gründen verzichtet haben. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'nichts-ist-unmglich.html'; this.page.identifier = '9271ed1c97_idnichts-ist-unmglich'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "nix-neues---.html": {
    "href": "nix-neues---.html",
    "title": "Nix Neues... - Get Blogged by JoKi",
    "summary": "Nix Neues... Copy Markdown View Markdown GitHub Markdown Source Nix Neues... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nix-neues---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nix-neues---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Nix Neues...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-26 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Der übliche, langweilige Sonntag ist hoffentlich bald vorüber... Irgendwie finde ich Sonn- und Feiertage total depremierend. Klar, man kann zuhause entspannen, mal dies und jenes im Haushalt erledigen, aber mir geht's meistens so, dass ich mir absichtlich Arbeit mache, damit ich was zu tun habe. \uD83D\uDE15 Dabei gäbe es sicherlich noch einige Dinge zu erledigen, aber auf diese hat man sonnatgs dann oftmals auch keinen 'Bock' - Nunja, ein wenig VFP programmiert, mit den Kumpels geschwätzt und 'ne DVD durchgezogen. Eigentlich ein erholsamer Tag... oder? Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'nix-neues---.html'; this.page.identifier = '9271ed1c97_idnix-neues---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "noch-ein-wiki.html": {
    "href": "noch-ein-wiki.html",
    "title": "Noch ein Wiki - Get Blogged by JoKi",
    "summary": "Noch ein Wiki Copy Markdown View Markdown GitHub Markdown Source Noch ein Wiki Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'noch-ein-wiki'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'noch-ein-wiki'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Noch ein Wiki') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-23 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Als ob ein Wiki nicht genug wäre... Nein, der Herr entwickelt ein eigenes System auf Basis der Active Foxpro Pages. Fairerweise sollte ich dabei erwähnen, dass ich bereits im August 2004 während meines mehrwöchigen Sommerurlaubs die Möglichkeit erhielt, das damalige Wiki der deutschsprachigen FoxPro Usergroup zu begutachten und ein wenig zu überarbeiten. Das Ergebnis dieses ersten Schrittes ist nun seit einiger Zeit als LiveFAQ der dFPUG online. Letztendlich war dieser Schritt nur ein Anfang und in der Zwischenzeit sind noch einige Ideen und Features auf meiner Festplatte herangereift. Nun, was soll ich noch sagen: A new star is born! Auf der eigens eingerichteten Website https://www.afpwiki.de ist eine WikiEngine auf Basis der Active FoxPro Pages eingerichtet. Die Entwicklung ist in Richtung OpenSource ausgelegt. Ich hoffe dabei, dass zum einen die Akzeptanz des AfpWiki schneller steigen wird, und zum anderen auch weitere Interessierte in die Weiterentwicklung einsteigen werden. Interessanterweise verwaltet die aktuelle Fassung des AfpWiki seine Daten in einem Microsoft SQL Server 2000 - konkret MSDE 2.0. Die nächsten Schritte der Entwicklung dürften potentiell folgendermaßen aussehen: Umstellung auf XHTML Erstellung von Stylesheets (CSS und XSL) Besserer Editor für die Pflege des Content Versionierung mit Differenzanzeige Für die weitere Zukunft sind dann noch weitere Features angedacht: Namespaces Import & Export Web Services Offline-Version etc... Für Anregungen, Kritik, etc. steht das FAQuarium zur Verfügung. Je nach Resonanz wird dort künftig eine neue Sektion (oder auch nicht) zum Thema AfpWiki eingerichtet. Okidoki, genug für gestern... \uD83E\uDD23 Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'noch-ein-wiki.html'; this.page.identifier = '9271ed1c97_idnoch-ein-wiki'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "noise-cancelling-on-windows-10.html": {
    "href": "noise-cancelling-on-windows-10.html",
    "title": "Noise Cancelling on Windows 10 - Get Blogged by JoKi",
    "summary": "Noise Cancelling on Windows 10 Copy Markdown View Markdown GitHub Markdown Source Noise Cancelling on Windows 10 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'noise-cancelling-on-windows-10'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'noise-cancelling-on-windows-10'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Noise Cancelling on Windows 10') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2020-05-21 - Filed under WindowsCommunity (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After finishing a full day of work and multiple meeting arrangements I accidentally broke one side of my headset. With the current confinement in Mauritius and scarcity of computer hardware I was looking into options to improve the use of my external Razer Seiren X microphone. Working from home (WFH) with the children most of the time around me it would be a challenge to use an \"open\" mic during conference calls on Microsoft Teams or Google Meet. Hence my search for a noise reduction solution that would allow me to work comfortable. Krisp I can't remember exactly but someone recommended Krisp - Noise Cancelling App some months back. And although I had a look at their website back then I didn't even download their application due to the pricing structure. That was definitely before they published their statement in regards to the COVID-19 situation: COVID-19 Response: New free plan and dropped prices Meanwhile I downloaded and even installed Krisp on one of my machines and it now snuggles nicely in the Systray area. The results are really good and its use is dead simple. I'm probably going to use Krisp on my Lenovo Yoga Book which doesn't have a secondary GPU. For Chromebooks Krisp offers an extension for the Chrome browser. Note: Krisp has more articles on noise cancelling on their blog: Best Noise Cancelling Apps for Windows and Best Noise Cancelling Apps for Mac. NVIDIA RTX Voice Next, I came across a notion of NVIDIA RTX Voice Setup Guide and I thought to give a try even though my main machine has a GeForce GTX 1060 only. NVIDIA RTX Voice is a new plugin that leverages NVIDIA RTX GPUs and their AI capabilities to remove distracting background noise from your broadcasts, voice chats, and remote video conferencing meetings. Other search results indicated that it would be possible, and so why not. After launching the 327 MB RTX Voice installer it performs a system check and told me to go away... Bummer! Following the instructions of other articles online and YouTube videos it is possible to by-pass this system check and get the installer to complete the job. Open Windows Explorer and navigate to the newly created folder C:\\temp\\NVRTXVoice\\NvAFX. There right-click on the file RTXVoice.nvi and choose Properties. Next, change to the tab Security and hit the Edit... button. After passing UAC select the LogonSessionId* user and give it Full Control in the permission matrix. Confirm all dialogs and then open the file RTXVoice.nvi with your favourite text editor. Between lines 14 and 16 is a <constraints> element that contains the blocking message from the failed system check. Delete those lines as highlighted in the image above and save the file. Finally, change to the parent folder C:\\temp\\NVRTXVoice and execute the file setup.exe to launch the RTX Voice installer again. Install NVIDIA RTX Voice after removing the constraints section The system check is now successful and the installation can be completed. Note: The temp folder can be removed now. After a compulsory reboot of Windows the NVIDIA RTX Voice app can be launched and configured. NVIDIA RTX Voice operates as a virtual sound routing device on your system. It's like a wrapper around your actual physical sound device. In NVIDIA RTX Voice you choose the input device and optionally the output device, and in your app(s) you select \"NVIDIA RTX Voice\" in the microphone/speaker settings. Refer to the NVIDIA RTX Voice: Setup Guide to configure other applications correctly. The results are impressive! The test calls in Teams and Skype were very positive and the feedback from conference calls was pleasing as well. Looks like I found myself a new audio setup. Until I am going to be able to purchase a new headset. Given feedback on social media I made another video to show the effect of noise cancelling provided by NVIDIA RTX Voice. Using various microphones and enabling NVIDIA RTX Voice The level of background noise during the recording was constantly the same. I hope that the impact of NVIDIA RTX Voice is audible. Image credit: Tai's Captures if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'noise-cancelling-on-windows-10.html'; this.page.identifier = '9271ed1c97_idnoise-cancelling-on-windows-10'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "normaler-tagesablauf.html": {
    "href": "normaler-tagesablauf.html",
    "title": "Normaler Tagesablauf? - Get Blogged by JoKi",
    "summary": "Normaler Tagesablauf? Copy Markdown View Markdown GitHub Markdown Source Normaler Tagesablauf? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'normaler-tagesablauf'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'normaler-tagesablauf'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Normaler Tagesablauf?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-22 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Der Tag müsste ein paar Stunden mehr haben. Die Umsetzung in MIB gefällt mir ziemlich gut, aber leider dürfte das bei uns utopisch sein. Es sei denn, Geschäfte und Behörden hätten mal bessere Öffnungszeiten, aber welcher Arbeitnehmer braucht das denn schon? Halben bis ganzen Tag Urlaub für mal aufs Amt oder zum Arzt zu gehen... Hey, wen interessiert's? An manchen Tagen (seit den letzten Monaten) wäre mir das sehr gelegen gekommen. Aber entweder war keine Zeit um morgens hinzugehen, weil man auf die Arbeit muss, oder abends wurde es im Büro länger und die Bürgersteige waren bereits hochgeklappt. Immerhin ist es ja schon angenehm, dass man bereits bis 20:00 einkaufen gehen kann, aber wenn man erst um 22:15 oder so heim kommt, dann bringt das ehrlich gesagt auch nicht besonders viel. Well, dafür gibt's ja die allwöchentliche Schlag- und Trettorgie am Samstag. Hm, habe ich mir auch schon länger abgewöhnt... Mist! Aber vllt. liegt die Ursache ja an anderer Stelle... \"All day work and no fun makes Jake a dull boy.\" - oder wie war das? Hm, egal. Aktuell bin ich ehrlich gesagt kaum in einer passablen Stimmungslage für einen Blogeintrag, aber schreiben soll ja angeblich helfen. Insofern schreibe ich einfach mal, was mir derzeit ein wenig im Weg rumbröckelt. Neben meiner aktuellen Arbeitszeit - cool, heute mal nur 14 Stunden gearbeitet - nervt mich seit Monaten wieder mal eine Selbsterhaltungssekte - die GEZ. Deren Anschreiben ist echt witzig... Allein der tolle Satz 'Sie sind gesetzlich dazu verpflichtet Auskunft zu geben, wenn Sie Rundfunkempfänger besitzen.' (oder so ähnlich). Okay, wenn ich welche hätte, bin ich dazu verpflichtet, aber wenn ich keine habe, bin ich auch nicht verpflichtet Auskunft zu geben. Oder sehe ich da irgendwas falsch? Aber wahrscheinlich habe sich bereits Tyraden von Rechtsanwälten Gedanken darüber gemacht. Nunja, die GEZ lebt lediglich ihren Selbsterhaltungstrieb aus. Höhöhö, ich sollte mal Kindergeld beantragen gehen... Kinder habe ich keine, aber die Fähigkeit zur Zeugung, also kann ich ja bereits prophylaktisch einfordern. Eyh, sind doch selbst schuld, wenn sie die Luft mit ihren öffentlich-rechtlichen Funkstrahlen verpesten... Ich hab' das nicht bestellt. Wäre ja noch schöner: Man setzt sich in eine Kneipe und bezahlt erst mal 8,60 Euro für Getränke, die man potentiell trinken könnte, aber nicht getrunken noch bestellt hat. \uD83E\uDD2A Aber hey, was wäre die GEZ ohne diesen gesetzlichen Auftrag? - Zum Glück nicht mehr vorhanden... Hm, doch mal wieder nachsehen, ob's nicht so'ne Aktion wie 'Brot für die GEZ' am Laufen ist. Aber selbst dann würde ich kein Geld spenden, sondern eher Konserven. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'normaler-tagesablauf.html'; this.page.identifier = '9271ed1c97_idnormaler-tagesablauf'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "not-attending-the-lugm-mini-meetup.html": {
    "href": "not-attending-the-lugm-mini-meetup.html",
    "title": "Not attending the LUGM mini-meetup - 05. Oct 2013 - Get Blogged by JoKi",
    "summary": "Not attending the LUGM mini-meetup - 05. Oct 2013 Copy Markdown View Markdown GitHub Markdown Source Not attending the LUGM mini-meetup - 05. Oct 2013 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'not-attending-the-lugm-mini-meetup'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'not-attending-the-lugm-mini-meetup'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Not attending the LUGM mini-meetup - 05. Oct 2013') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-10-06 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Not attending a meeting of the LUGM can be fun, too. It's getting a bit of a habit that Ish is organising small gatherings, aka mini-meetups, of the Linux User Group Mauritius/Meta (LUGM) almost every Saturday. There they mainly discuss and talk about various elements of using Linux as ones main operating systems and the possibilities you are going to have. On top of course, some tips & tricks about mastering the command line and initial steps in scripting or even writing HTML. In general, sounds like a good portion of fun and great spirit of community. Unfortunately, I'm usually quite busy with private and family matters during the weekend and so I already signalised that I wouldn't be around. Well, at least not physically... But this Saturday a couple of things worked out faster than expected and so I was hanging out on my machine. I made virtual contact with one of Pawan's messages over on Facebook... And somehow that kicked off some kind of an online game fun on basic configuration of Apache HTTPd 2.2.x, PHP 5.x and how to improve the overall performance of a newly installed blog based on WordPress. Default configuration files Nitin's website finally came alive and despite the dark theme and the hidden Apple 'fanboy' advertisement I was more interested in the technical situation. As with any new installation there is usually quite some adjustment to be done. And Nitin's page was no exception. Unfortunately, out of the box installations of Apache httpd and PHP are too verbose and expose too much information under the hood. You might think that this isn't really a problem at all, well, think about it again after completely reading this article. First, I checked the HTTP response headers - using either Chrome Developer Tools or Firefox Web Developer extension - of Nitin's page and based on that I advised him to lower the noise levels a little bit. It's not really necessary that detailed information about web server software and scripting language has to be published in every response made. Quite a number of script kiddies and exploits actually check for version specifics prior to an attack. So, removing at least version details hardens the system a little bit. In particular, I'm talking about these response values: Server X-Powered-By How to achieve that? By tweaking the configuration files... Namely, we are going to look into the following ones: apache2.conf httpd.conf .htaccess php.ini The above list contains some additional files, I'm talking about in the next paragraphs. Anyway, those are the ones involved. Tweaking Apache Open your favourite text editor and start to modify the apache2.conf. Eventually, you might like to have a quick peak at the file to see whether it is necessary to adjust it or not. Following is a handy combination of commands to get an overview of your active directives: # sudo grep -v '#' /etc/apache2/apache2.conf | grep -v '^$' | less There you keep an eye on those two Apache directives: ServerSignature Off ServerTokens Prod If that's not the case, change them as highlighted above. In order to activate your modifications you have to restart Apache httpd server. On Debian and Ubuntu you might use apache2ctl for that, on other distributions you might have to use service or run the init-scripts again: `# sudo apache2ctl configtest Syntax OK sudo apache2ctl restart` Refresh your website and check the HTTP response header. Tweaking PHP5 (a little bit) Next, check your php.ini file with the following statement: # sudo grep -v ';' /etc/php5/apache2/php.ini | grep -v '^$' | less And check the value of expose_php = Off Again, if it's not as highlighted, change it... Some more Apache love Okay, back to Apache it might also be interesting to improve the situation about browser caching and removing more obsolete information. When you run your website against the usual performance checks like Google Page Speed and Yahoo YSlow you might see those check points with bad grades on a standard, default configuration. Well, this can be done easily. Configure entity tags (ETags) ETags are only interesting when you run your websites on a farm of multiple web servers. Removing this data for your static resources is very simple in Apache. As we are going to deal with the HTTP response header information you have to ensure that Apache is capable to manipulate them. First, check your enabled modules: # sudo ls -al /etc/apache2/mods-enabled/ | grep headers And in case that the 'headers' module is not listed, you have to enable it from the available ones: # sudo a2enmod headers Second, check your httpd.conf file (in case it exists): # sudo grep -v '#' /etc/apache2/httpd.conf | grep -v '^$' | less In newer (better said fresh) installations you might have to create a new configuration file below your conf.d folder with your favourite text editor like so: # sudo nano /etc/apache2/conf.d/headers.conf Then, in order to tweak your HTTP responses either check for those lines or add them: Header unset ETag FileETag None In case that your file doesn't exist or those lines are missing, feel free to create/add them. Afterwards, check your Apache configuration syntax and restart your running instances as already shown above: `# sudo apache2ctl configtest Syntax OK sudo apache2ctl restart` Add Expires headers To improve the loading performance of your website, you should take some care into the proper configuration of how to leverage the browser's ability to cache certain resources and files. This is done by adding an Expires: value to the HTTP response header. Generally speaking it is advised that you specify a near-future, read: 1 week or a little bit more, for your static content like JavaScript files or Cascading Style Sheets. One solution to adjust this is to put some instructions into the .htaccess file in the root folder of your web site. Of course, this could also be placed into a more generic location of your Apache installation but honestly, I'd like to keep this at the web site level. Following some adjustments I'm currently using on this blog site: # Turn on Expires and set default to 0 ExpiresActive On ExpiresDefault A0 # Set up caching on media files for 1 year (forever?) <FilesMatch \"\\.(flv|ico|pdf|avi|mov|ppt|doc|mp3|wmv|wav)$\"> ExpiresDefault A29030400 Header append Cache-Control \"public\" </FilesMatch> # Set up caching on media files for 1 week <FilesMatch \"\\.(js|css)$\"> ExpiresDefault A604800 Header append Cache-Control \"public\" </FilesMatch> # Set up caching on media files for 31 days <FilesMatch \"\\.(gif|jpg|jpeg|png|swf)$\"> ExpiresDefault A2678400 Header append Cache-Control \"public\" ```` As we are editing the .htaccess files, it is not necessary to restart Apache. In case that your web site doesn't load anymore or you're experiencing an error while trying to restart your httpd, check that the 'expires' module is actually an enabled module: ` # ls -al /etc/apache2/mods-enabled/ | grep expires sudo a2enmod expires ` Of course, the instructions above a re not feature complete but I hope that they might provide a better default configuration for your LAMP stack. Resume of the day Within a couple of hours, and while being occupied with an eLearning course on SQL Server 2012, I had some good fun in helping and assisting other LUGM members while they were some kilometers away at Bagatelle. According to other blog articles it seems that Nitin had quite some moments of desperation. Just for the records: At no time it was my intention to either kick his butt or pull a leg on him. Simply, providing some input based on the lessons I've learned over the last couple of years configuring Apache HTTPd and PHP. Check out the other blogs, too: LUGM mini-meetup... Epic! Superb Saturday Linux Meetup And last but not least, the man himself: The end of a new beginning Cheers, and happy community'ing! Updates Due to our weekly Code & Coffee sessions in the MSCC community, I had a chance to talk to Nitin directly and he showed me the problems directly on his machine. This led to update this article hence the paragraphs on enabling the modules 'headers' and 'expires'. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'not-attending-the-lugm-mini-meetup.html'; this.page.identifier = '9271ed1c97_idnot-attending-the-lugm-mini-meetup'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "nuget-better-release-notes-versioning.html": {
    "href": "nuget-better-release-notes-versioning.html",
    "title": "NuGet: Better release notes and versioning - Get Blogged by JoKi",
    "summary": "NuGet: Better release notes and versioning Copy Markdown View Markdown GitHub Markdown Source NuGet: Better release notes and versioning Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nuget-better-release-notes-versioning'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'nuget-better-release-notes-versioning'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'NuGet: Better release notes and versioning') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-03-31 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Providing release notes for a NuGet package is an essential part of the development and distribution of the package itself. Commonly it shall be the first source of information to describe latest updates regarding the improvements and changes. However, the current way of authoring release notes in a .NET project is sub-optimal. The following approach might offer a simpler way to achieve this including formatting and additional purpose on GitHub. The situation (you might be facing) On the one side, the Release notes element or PackageReleaseNotes of the MSBuild property in the .csproj project file is quite limited regarding formatting options. And it is cumbersome to update it with release information continuously. Especially while using the single line text entry in the properties page of Visual Studio. Other the other hand, the PackageReadmeFile element provides a bit more freedom to describe a package, however, it is linked only. And on top of that, it is not even exposed in the user interface of the project properties. So, having an external resource like a Markdown file that is not only linked could be an interesting alternative. The information is stored outside the project file, is easier to edit, and provides rich formatting options including images, bullet lists and hyperlinks. A solution Luckily, similar to pushing a package to a network share or to a NuGet source feed we can leverage another MSBuild task to take the content of that Markdown file and inject it into the package description. Note: For a code base that is hosted in a (public) repository on GitHub (or BitBucket, Azure DevOps, Google Cloud Source Repositories, etc) using CHANGELOG.md instead of release notes might be a better choice of file name. Let's start with a fresh CHANGELOG.md that we place in the root of the project folder. # Changelog (Release Notes) All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) (SemVer). ## [Unreleased] ### Added ### Changed ### Fixed As you might see in the file content already we are following a suggested format to maintain the change log and we are establishing Semantic Versioning right from the beginning. The content is written in Markdown and apart from using your favourite IDE which might have an integrated viewer for Markdown it is a commonly used and understood formatting syntax among developers. Markdown can also be transformed to other output formats like HTML or PDF. Think of pandoc as a utility tool to generate a different output, or maybe any kind of static web page generator like Hugo, et al. Now, with the CHANGELOG.md file in place (and under version control) it is time to use an MSBuild task to read the content of the file and inject it into the project properties. Add a new Target element in your .csproj file with the following content, probably near the bottom area. <Target Name=\"InjectPackageReleaseNotesFromFile\" BeforeTargets=\"GenerateNuspec\" Condition=\"Exists('CHANGELOG.md')\"> <PropertyGroup> <PackageReleaseNotes>$([System.IO.File]::ReadAllText(\"$(MSBuildProjectDirectory)/CHANGELOG.md\"))</PackageReleaseNotes> </PropertyGroup> </Target> According to the BeforeTarget attribute this task is executed before generating the NuGet specification file .nuspec. A process that happens after successful compilation of the project or solution and as the name suggests before the actual NuGet package is created. Sounds perfect. The task itself creates or updates the XML element PackageReleaseNotes in the project file and replaces the value with the complete content of the CHANGELOG.md file. To avoid any compilation issues the Condition attributes checks for the existence of the referenced file prior to executing the task. To keep yourself and maybe other team members informed, place the following remark in the original Release notes of the project properties. <PackageReleaseNotes>(Package release notes are in CHANGELOG.md)</PackageReleaseNotes> Alternatively, leave it blank. The result Using Markdown to create richly formatted release notes compared to plain text might be a welcoming piece of documentation. Here's how it looks in the NuGet Gallery. In comparison the same release notes on GitHub. With a small amount of extra work same CHANGELOG.md could be used as a source of GitHub Pages and be part of a generated, static website of your project. What's got that to do with versioning? The change log is already using semantic versioning to document changes. So, while already using this approach to inject the release notes into the NuGet package... Why not doing the same for the version of the package. Instead of fiddling around in the project properties or updating the .csproj file directly, wouldn't be better to have a VERSION file in the root folder and its content is replacing the Version element during the compilation process. Put the following lines once in your project file, and it's done. <Version>$([System.IO.File]::ReadAllText(\"$(MSBuildProjectDirectory)/../../VERSION\"))</Version> The path might be different for you and depending on how you structured your project. I put that VERSION file into the root folder of the repository because I'm using it for multiple projects simultaneously. A lazy way to keep the version information of related projects / packages in the same solution synchronized. Both approaches - CHANGELOG.md and VERSION - are used in the NuGet packages Mscc.GenerativeAI*. It's an open-source library to integrate Google Gemini into .NET applications. The repository of Gemini AI Client for .NET and ASP.NET Core is hosted on GitHub. Final thought Apart from maintaining those two files in the repository manually, they could have been generated or updated automatically by a continuous integration (CI) pipeline. However, I leave that to you. Perhaps a future blog article, who knows... Image credit: Image Creator from Microsoft Designer using prompt: Create an image depicting versioning of packages if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'nuget-better-release-notes-versioning.html'; this.page.identifier = '9271ed1c97_idnuget-better-release-notes-versioning'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "objektorientierung-fr-anfnger.html": {
    "href": "objektorientierung-fr-anfnger.html",
    "title": "Objektorientierung für Anfänger... - Get Blogged by JoKi",
    "summary": "Objektorientierung für Anfänger... Copy Markdown View Markdown GitHub Markdown Source Objektorientierung für Anfänger... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'objektorientierung-fr-anfnger'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'objektorientierung-fr-anfnger'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Objektorientierung für Anfänger...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-20 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nach dem wir die Grundkonzepte und die wesentlichen Unterschiede sowie Vor- und potentiellen Nachteile der beiden Entwicklungssysteme .NET (C# und VB) und VFP durchgegangen sind, sehen wir uns nun einmal an, wie sich Programmierung im .NET Framework \"anfühlt\". Please fasten your seat belts, we are ready to take off... Als IDE setzen wir Visual Studio 2005 ein - was auch sonst? \uD83D\uDE09 - und erzeugen sozusagen unsere erste Solution. Als erstes Projekt schauen wir uns das wichtigste für einen VFP-Entwickler an: Den Zugriff auf die Daten. Da es keine direkte Datenbindung in .NET gibt, aber potentiell mit .NET 3.0 in Form von LINQ nachgereicht werden wird, erstellen wir als zuerst eine Bibliothek, die den Datenzugriff möglichst transparent gestaltet. Da wir wissen, dass es mehr als SQL Server als Datenbankserver gibt, verwenden wir auch direkt die Interfaces statt der Klassen und legen munter los. Nach einigen klärenden Fragen und Antworten haben wir schon einmal eine lauffähige Komponente. Okay, aber aktuell nutzt uns das noch nicht wirklich viel... Wir wollen auch was sehen. Gesagt, getan. Als nächsten erstellen wir eine Webanwendung, die unsere Komponente nutzt, um die Datensätze zu holen und im Browser auszugeben. Ahhh, nett, dass Visual Studio seinen eigenen Webserver - übrigens nicht Cassini - mitbringt und damit das Ausführen und Debuggen ohne IIS ermöglicht. Wirklich nettes Feature, muss man schon sagen. So nun erstellen wir eine Windows Forms Anwendung und nutzen, oh Wunder, dabei wieder die Datenkomponente. Nett, zusammenstecken und funktioniert. Klar, dass das bei simplen Beispielen auf eine Tabelle noch keine Kopfschmerzen bereitet. Und gleich der nächste Gimmick: ClickOnce-Deployment. Schliesslich gilt es ja nicht nur die Anwendung zu schreiben, sondern diese soll ja auch mal beim Kunden auf dem System landen und frisch ausgeführt werden. Die Einstellungsmöglichkeiten im Visual Studio zu ClickOnce sind ausreichend für das grundsätzliche Deployment. Für die durchschnittlichen VFP-Projekte ist sozusagen alles dabei. Nett, aber wer sich mit professionellen Installerprodukten beschäftigt, vermisst auf den ersten Blick doch das ein oder andere Feature. Nunja, mal irgendwann genauer mit der Materie beschäftigen und berichten. Well, Datenzugriff, Webanwendung, Windows Anwendung... Achja, SmartClient fehlt noch. Auch hier eine simple Sache. Datenkomponente verknüpfen, den Designer anwerfen, zwei, drei Controls auf die Form werfen, bisschen Code dazwischen kleben und im Emulator laufen lassen. Wer hätte es gedacht, fertig ist die erste SmartClient-Anwendung. Tjoa, liebe Leute, dass ist der Grund wieso man Komponenten schreiben sollte... Alright, wir haben inzwischen den Tagesabschluß erreicht und werden dem Abendessen fröhnen. Für mich war am heutigen Tag bis auf den coolen Trick mit den DataServices-Klassen in Kombination mit Stored Procedures kaum was Neues dabei. Dennoch ölt die Wiederholung die richtige Verdrahtung in den eigenen Hirnwindungen und dient gleichermaßen als Überprüfung des bereits Gelernten. Keine Überraschungen feststellbar. So, nun aber doch mal was futtern... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'objektorientierung-fr-anfnger.html'; this.page.identifier = '9271ed1c97_idobjektorientierung-fr-anfnger'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "on-a-mission-from-god.html": {
    "href": "on-a-mission-from-god.html",
    "title": "On a mission from god - Get Blogged by JoKi",
    "summary": "On a mission from god Copy Markdown View Markdown GitHub Markdown Source On a mission from god Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'on-a-mission-from-god'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'on-a-mission-from-god'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'On a mission from god') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-01-27 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Das erste Treffen der Mitglieder vom CLIP Community Programm 2006 steht an. Und dieses Mal ist es aus mehreren unterschiedlichen Gründen spannend. Zunächst einmal sei gesagt, dass wir uns nicht wie bei allen bisherigen Treffen im bavarischen Großstadtbereich von München zusammenfinden, sondern dieses Mal geht's in den Nordwesten Deutschlands. Genauer gesagt in die Heimat der längsten Theke der Welt - Düsseldorf. Weiterhin ist es eigentlich ein exklusives Treffen für CLIP Mitglieder - sozusagen ein CLIP Summit... Und wir sind dieses Mal nicht in irgendwelchen Konferenzräumen unter, sondern laut den bisherigen Announcements in der Gaststätte Das Uerige. Also, absolut lockere Atmosphäre und IMHO auch das geeignete Umfeld für Community-Angelegenheiten. Zumindest 'meine' Community trifft sich monatlich ebenfalls in einer Gaststätte und das mit ausschliesslich positiven Erfahrungen. Vielleicht lässt sich ja dadurch das ein oder andere CLIP Mitglied ebenfalls dazu bewegen ein Offlinetreffen zu organisieren. Die Jungs von MyCSharp.de haben es ja schon letztes Jahr erfolgreich vorgemacht. Schade, dass ich keine Zeit hatte... Unser Treffen wird sich hoffentlich vor allem wieder um die Kernbelange von CLIP annehmen. Und... so ebenfalls meine Hoffnung, dass wieder konstruktive Vorschläge, Ideen und Konzepte für die Aufgaben der Community herausspringen. Ich muss ehrlich gesagt gestehen, dass ich leider - aus unterschiedlichsten Gründen - meine derzeitige Community-Website AFP FAQ ein wenig vernachlässigt habe. Begonnen hat der Spass mit dem kompletten Serverauswahl und meiner intensiven Recherche in den Internetcaches der Welt, um zumindest mal den bisherigen Content wieder zusammen zu tragen. Hm, von den existierenden 310 Artikeln habe ich wenigstens round about 267 Artikel restaurieren können. Nunja, aber die Programmierung der Website in Visual FoxPro bzw. Active FoxPro Pages ging halt leider auch flöten... Weiterhin ergaben sich in den letzten Monaten auch einige Änderungen in meinem beruflichen Umfeld, so dass für meine 'Freizeitaktivitäten' leider nicht sonderlich viel übrig blieb. Daher freue ich mich umso mehr, dass ich heute ein paar Überstunden abfeiern kann. Eine optimale Grundlage für das heutige Zusammenkommen, wie ich finde... Oder? Nach einem kurzen, morgendlichen Abstecher in die Firma befinde ich mich aktuell im Zug nach Bingen, um anschliessend direkt nach Düsseldorf weiter zu fahren. Aber... musste es denn ausgerechnet heute sein? g - Leute, heute ist bundesweite Zeugnisausgabe in den Schulen! Die Abfahrt in Kaiserslautern war ja noch ganz okay, was die Verfügbarkeit von Sitzgelegenheiten angeht, aber ab Winnweiler, über Rockenhausen bis Alsenz... Da wurde es dann ein wenig 'kriminell' \uD83D\uDE09 Wenn ich mich an meine Schulzeit zurückerinnere, war das beste am Tag der Zeugnisausgabe die Tatsache, dass man nur vier Stunden Unterricht hatte und dann ins Wochenende entlassen wurde. Die Jugend ist glücklicherweise genauso ausgelassen und gut gelaunt, wie wir es vor etlichen Jahren waren. Und das ist gut so. Hm, gerade mal die Funkschnittstellen vom Notebook deaktiviert, damit die Akkuleistung länger ausharrt. Schon spannend mit der neuen Kiste unterwegs zu sein. Hehehe, erinnert mich spontan an Men In Black 2, als sich J und K nach dem Deneutralizer einigen, wer denn nun das Auto fährt. Flupp - spontaner Umstieg in Bingen und weiter geht's im IC für die nächsten zwei Stunden. Hm, ungewöhnlich, aber dadurch werde ich zu früh am Treffpunkt sein. Naja, schauen wir mal, was unterwegs noch alles (oder nichts) passieren wird. So, weiter geht's zu den musikalischen Klängen von The Sins of Thy Beloved. Aktuell programmiere ich noch ein wenig an der AFP-Webanwendung für die Termin- und Themendarstellung der VFP Usergroup in Speyer. Hier war, ehrlich gesagt, längst mal Großreinemachen angesagt. Das Feedback bei der kurzen Vorstellung auf dem letzten Stammtisch war glücklicherweise schon mal positiv. Ich bin ebenfalls mal auf das finale Ergebnis gespannt \uD83D\uDE0E Koblenz, 13:46 - die Frisur hält. Aber das Zugabteilung füllt sich wieder mit einer Unmenge an Menschen. Herrje, wo wollen die denn nur alle hin? Gibt's irgendwo Richtung Norden gratis Schnittchen, oder dergleichen? \uD83D\uDE01 Wenigstens stehen ausreichend Sitzplätze für die Reisenden zur Verfügung. Ich hab's mir 'auf dem Gang' gemütlich gemacht. Naja, Gang kann man eigentlich nicht sagen. Der IC bietet hier im Wagon einen Mittelgang an - auf der einen Seite längs stehen Einzelsitzplätze mit Tisch, und auf der anderen Seite quer die gewohnten Abteils; jedoch mich vier statt sechs Sitzgelegenheiten, zur Verfügung. Ihr könnt euch ja denken, wo ich aktuell rumlungere... Hm, die verbleibende Akkuleistung lässt eine Schätzung zu, dass es genau mit der Ankunft in Düsseldorf aus sein wird. Das passt ja wieder wie die Faust aufs Auge. Lassen wir uns mal von der Wirklichkeit überzeugen und schauen wie weit wir kommen werden. Achja, inzwischen lausche ich den Symphonien von Theatre of Tragedy. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'on-a-mission-from-god.html'; this.page.identifier = '9271ed1c97_idon-a-mission-from-god'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "on-the-rail-again.html": {
    "href": "on-the-rail-again.html",
    "title": "On the rail again... - Get Blogged by JoKi",
    "summary": "On the rail again... Copy Markdown View Markdown GitHub Markdown Source On the rail again... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'on-the-rail-again'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'on-the-rail-again'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'On the rail again...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-13 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); In Anlehnung des Standardspruchs \"On the road again...\" schreibe ich diesen Beitrag gerade direkt im RegionalExpress zurück in die Heimat. Puuuh Leute, ich bin ehrlich gesagt mehr als glücklich, dass die letzten vier Tage relativ stressfrei und vor allem mit sehr viel Spass, Community-Aktivität und Networking über die Bühne gegangen sind. Wie in den meisten Fällen bei solchen Events lag mein tägliches Schlafpensum bei ca. drei bis max. vier Stunden pro Nacht. Leider konnte ich meine Sessionpräsentationen und die Beispiele nicht vollständig vor der Konferenz fertigstellen bzw. hatte noch durch Anregungen anderer Konferenzsprecher und Teilnehmer bekommen, die ich noch gerne verwerten wollte. Daher gab's dann doch wieder verlängerte Arbeitstage, äh, verkürzte Nächte... Well, die Gesundheit macht's aktuell mit und ich habe mir auch bereits anderweitige Konzepte für künftige Sessions ausgedacht. Mal sehen, ob und wann ich diese umsetzen, respektive praktizieren kann. Auf alle Fälle waren die Ergebnisse meiner nächtlichen Aktivitäten bis auf kleinere Patzer - in der Subversion-Session wollte der PushOK SVN SCC Proxy nicht so richtig mit dem Repository harmonieren und in der Linux-Session stresste die Tasenwiederholung während der Ausführung von Visual FoxPro unter wine. Nunja, shit happens and life goes on... Was mir dieses Jahr extremst positiv aufgefallen ist (und auch unter den Redner angesprochen wurde) ist der hohe Anteil an a) jungen und b) weiblichen Teilnehmern gewesen. Ein richtiger Hammer! Anfangs dachte ich, dass die evtl. falsch wären oder in einer parallel stattfindenden Veranstaltung entschwinden würden, aber nix da... Volles Programm in den Sessions abgehangen. Apropos Sessions... Hier eine flüchtige Reflexion meiner Eindrücke während meiner Vorträge: Hohe Zurückhaltung mit Fragen, Anmerkungen und Diskussion - einige \"trauten\" sich wenigstens nach der Session vor zum Rednerplatz. Naja, ich persönlich bevorzuge den Dialog - genau wie auffem Stammtisch eben... Kollegiales \"Du\"zen nach meiner Rückfrage, ob ich offiziell 3. Person Plural oder 2. Person Plural verwenden soll? Hat zumindest mir sehr gut gefallen und zugesagt, da hierbei eine weitere Hemmschwelle (siehe 1. Punkt) fällt und Fragen potentiell leichter von der Hand gehen, als wenn distanziert miteinander gesprochen wird. Wir sind auf einer Entwicklerkonferenz und nicht auf einem Business Market... Ich bin zeitlich sehr gut hingekommen und die vermittelten Beispiele waren - laut Feedback - klar und schlüssig verständlich. Genauere Ergebnisse wird die Auswertung der Bewertungsbögen mit sich bringen. Ich bin mal gespannt wieviel schlechter ich gegenüber dem letzten Jahr abschneiden werde... Insgesamt bleibt mir nur zu sagen, dass ich Teilnehmer/in, die nicht an diesem grossartigen Ereignis in der deutschen VFP-Entwicklerszene teilgenommen haben, über das Jahr bis zur nächsten Konferenz ermuntern werde 2006 die Chance zu nutzen. Es ist wirklich genial, wenn man sich über drei Tage lang untereinander austauschen kann. Neben den Kontakten über die VFP-Newsgruppe sind sehr viele Aktivisten aus dem dFPUG-Forum anwesend. Selbstverständlich sind auch Stammtischgänger vor Ort anzutreffen. Ich bin mal auf den nächsten Stammtisch am 16.11.2005 gespannt... Dort wird's garantiert einiges zu erzählen geben. Herrje, da fällt mir ja ein, dass die Slides für den Abend noch zu erstellen sind... - Hm, PostgreSQL 8.0... wir werden sehen, was sich an Informationen zusammentragen lässt. Ich habe auf alle Fälle bereits jetzt meinen VOrsatz für 2006 bestimmen können: Mehr Community-Aktivität... Neben den monatlichen Stammtisch werde ich versuchen den ein oder anderen Artikel für den FoxX Professional zu schreiben. Mal sehen, ob sich da nicht evtl. eine Serie draus machen lösst... Auf alle Fälle habe ich wieder coole Neuigkeiten in diesem Bereich erfahren. Hehehe, die Wintermonate eignen sich ja hervorragend zum Schreiben und Programmieren. Also, ich bin selbst mal gespannt... Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'on-the-rail-again.html'; this.page.identifier = '9271ed1c97_idon-the-rail-again'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "open-powershell-window-here.html": {
    "href": "open-powershell-window-here.html",
    "title": "Tip: Open PowerShell window here - Get Blogged by JoKi",
    "summary": "Tip: Open PowerShell window here Copy Markdown View Markdown GitHub Markdown Source Tip: Open PowerShell window here Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'open-powershell-window-here'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'open-powershell-window-here'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Tip: Open PowerShell window here') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-01-11 - Filed under Windows (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); As a power user I'm more used to hotkeys and application shortcuts than moving the mouse pointer on the screen. Actually, I'm too lazy to lift my hand from the keyboard to either move my trackball or fiddle around on the trackpad. It is also a matter of performance and health. Constant change of position of your hand between keyboard and a mouse pointing device, like ie. a mouse, a trackpad, a mouse knob, or a trackball, takes time and is tiring for shoulder and neck. Following are a few options of how to open an instance of Windows PowerShell in the current folder. Most probably they are ordered by personal preference. Hotkey: Alt + F, then R This seems to be the fastest option available. It's more like an unconscious reflex than real decision making process to open a new PowerShell window wherever I'm currently residing in Windows Explorer. Hotkey: F4, then type 'powershell' I used to start with a modified version of this hotkey. Previously, I clicked into the address bar to focus it instead of using the F4 key. Both options work as expected. Give it a try. Note: Typing cmd will launch a Command Prompt. Entering bash gives access to GNU bash on Windows 10. Hotkey: Shift + F10, then S That one can be a bit tricky because the hotkey Shift + F10 opens the extended context menu. The available entries in the context menu are obviously specific to the context or state in which the mouse is currently placed. And that's where is approach falls short. Given the right context of a folder the menu entry is available. Pressing the letter S selects the menu entry 'Open Power S hell window here' and then hit Enter to confirm the choice. While moving around in the Navigation pane it always works for the currently selected folder. However, in the Content pane this hotkey is not practical. Once a file has been selected you won't get access to the folder context menu anymore. Shifting focus will give you all kind of different context menus. I wouldn't recommend this hotkey. See for yourself. Shift + Right-click Similar to the Shift + F10 hotkey previously mentioned the context menu can be opened with right-click on a mouse pointing device. Unfortunately, the option to launch Windows PowerShell is only available in the extended context menu; hence holding down Shift before right-click is compulsory. Bonus: Shift + Menu key, then S A regular keyboard most probably might provide a designated Menu key which allows the user to launch the context menu without having to right-click. Same constraints as already mentioned above apply to this approach. Extended entries in regular context menu While searching for additional options to launch Windows PowerShell from the current folder I read that an entry could be 'transferred' permanently into the regular context menu. Open the Registry Editor - Win + R > regedit > Enter - and navigate to the following keys under HKCR: HKEY_CLASSES_ROOT\\Directory\\shell\\Powershell HKEY_CLASSES_ROOT\\Drive\\shell\\Powershell Either remove or rename the key Extended to enable the entry in the regular context menu. Windows 10 did not allow me to change the Extended key. Perhaps this might be limited to previous builds or versions of Windows 10. If you were able to complete the transfer, let me know in the Comment section below. Start PowerShell: Win + X, then either I or A Before we start to talk about the option to generally launch an instance of Windows PowerShell with either regular or administrative permissions, it is necessary to check the Taskbar settings on your computer. Open the Start menu and click on Settings > Personalisation > Taskbar. Ensure the option 'Replace Command Prompt with Windows PowerShell in the menu when I right-click the start button or press Windows logo key + X' is set to On. Image credit: Jochen Kirstätter if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'open-powershell-window-here.html'; this.page.identifier = '9271ed1c97_idopen-powershell-window-here'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "openvpn-on-windows-all-tap-windows-adapters-on-this-system-are-currently-in-use.html": {
    "href": "openvpn-on-windows-all-tap-windows-adapters-on-this-system-are-currently-in-use.html",
    "title": "OpenVPN: All TAP-Windows adapters on this system are currently in use - Get Blogged by JoKi",
    "summary": "OpenVPN: All TAP-Windows adapters on this system are currently in use Copy Markdown View Markdown GitHub Markdown Source OpenVPN: All TAP-Windows adapters on this system are currently in use Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'openvpn-on-windows-all-tap-windows-adapters-on-this-system-are-currently-in-use'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'openvpn-on-windows-all-tap-windows-adapters-on-this-system-are-currently-in-use'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'OpenVPN: All TAP-Windows adapters on this system are currently in use') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-08-31 - Filed under DevelopmentLinuxWindows (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Working with several clients or partners might be an interesting challenge sometimes. While adding a new connection to an existing OpenVPN infrastructure I came across the following error message in the client log file: All TAP-Windows adapters on this system are currently in use. Depending on how you actually installed your VPN client software you might be facing this issue while adding an additional client configuration for another connection. Especially when you are using a client software by a third-party provider, ie. WatchGuard Mobile VPN or Sophos. Perhaps you might be struggling to resolve it. Get the TAP-Windows driver Check whether you have the full installation of OpenVPN software. If yes, you might like to skip this the following steps and directly move on to add another TAP adapter to your Windows system. Otherwise, please navigate to the Community Downloads of OpenVPN and either get the latest OpenVPN package, or if you think that this might be an issue, scroll down a little bit on same page and get Tap-windows package for your system. After the download is complete, run the installation routine and make sure to select TAP Virtual Ethernet Adapter like so: You might have to reboot Windows to complete the network driver installation. Add a new TAP virtual ethernet adapter Now, you should be able to add an additional TAP interface to your system, and make it available for your new OpenVPN connection. Hit the Start button or press the Win key, then type tap and wait for Windows to give you its matches found on the system. Here is how it looks like on my Windows 10: Click on the entry Add a new TAP virtual ethernet adapter and confirm the User Account Control (UAC) dialog with Yes. You then see an administrative command prompt that adds another network interface to your Windows. C:\\WINDOWS\\system32>rem Add a new TAP virtual ethernet adapter C:\\WINDOWS\\system32>\"C:\\Program Files\\TAP-Windows\\bin\\tapinstall.exe\" install \"C:\\Program Files\\TAP-Windows\\driver\\OemVista.inf\" tap0901 Device node created. Install is complete when drivers are installed... Updating drivers for tap0901 from C:\\Program Files\\TAP-Windows\\driver\\OemVista.inf. Drivers installed successfully. C:\\WINDOWS\\system32>pause Press any key to continue . . . And your OpenVPN client is ready to roll. The shortcut below the Windows Start menu is linked to a batch file which you can also access and launch directly from %ProgramFiles%\\TAP-Windows\\bin Note: Ensure to run the batch file with administrative permissions. Otherwise, the driver installation will fail. Review your existing Network Connections Perhaps you would like to inspect the existing TAP-Windows Adapters? You find them in the Control Panel under Network Connections. The adapters are classified as TAP-Windows Adapter V9. Here you can enable, disable or even delete an existing network interface. Some readers might prefer interaction with a command line interface (CLI). Well, even on Windows there is nothing to worry about this. The Network Shell (Netsh) of Windows has you covered, although it is recommended to use PowerShell to manage networking technologies: PS C:\\> Get-NetAdapter Name InterfaceDescription ifIndex Status ---- -------------------- ------- ------ vEthernet (Default Swi... Hyper-V Virtual Ethernet Adapter 30 Up Wi-Fi Killer Wireless-n/a/ac 1535 Wireless... 28 Up Ethernet Killer E2500 Gigabit Ethernet Contro... 19 Disconnected Ethernet 4 TAP-Windows Adapter V9 #2 15 Disconnected VMware Network Adapte...8 VMware Virtual Ethernet Adapter for ... 14 Up VMware Network Adapte...1 VMware Virtual Ethernet Adapter for ... 13 Up Ethernet 2 ThinkPad USB-C Dock Ethernet 8 Disconnected Ethernet 5 TAP-Windows Adapter V9 #3 52 Up VirtualBox Host-Only ...2 VirtualBox Host-Only Ethernet Adap...#2 6 Up Ethernet 3 TAP-Windows Adapter V9 5 Up The information provided is identical to the visual representation in Windows Explorer. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'openvpn-on-windows-all-tap-windows-adapters-on-this-system-are-currently-in-use.html'; this.page.identifier = '9271ed1c97_idopenvpn-on-windows-all-tap-windows-adapters-on-this-system-are-currently-in-use'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "openvpn-revisited.html": {
    "href": "openvpn-revisited.html",
    "title": "OpenVPN re-visited - Get Blogged by JoKi",
    "summary": "OpenVPN re-visited Copy Markdown View Markdown GitHub Markdown Source OpenVPN re-visited Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'openvpn-revisited'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'openvpn-revisited'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'OpenVPN re-visited') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2018-08-28 - Filed under LinuxPersonal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It's been a very long time since I set up the VPN infrastructure at the office using OpenVPN. Today, I came across an interesting log entry that I would like to document quickly. At the time of writing I have OpenVPN 2.4.6 running on my Windows 10 machine. The existing infrastructure though is on a different version, and this morning I observed the following entries in the log file: Tue Aug 28 08:50:09 2018 WARNING: INSECURE cipher with block size less than 128 bit (64 bit). This allows attacks like SWEET32. Mitigate by using a --cipher with a larger block size (e.g. AES-256-CBC). Tue Aug 28 08:50:09 2018 WARNING: INSECURE cipher with block size less than 128 bit (64 bit). This allows attacks like SWEET32. Mitigate by using a --cipher with a larger block size (e.g. AES-256-CBC). Tue Aug 28 08:50:09 2018 WARNING: cipher with small block size in use, reducing reneg-bytes to 64MB to mitigate SWEET32 attacks. Curious about those entries I found Sweet32: Birthday attacks on 64-bit block ciphers in TLS and OpenVPN as an informative reference on the documented vulnerabilities CVE-2016-2183 and CVE-2016-6329. There I found the connection back to OpenVPN. Which is also described on the official wiki: OpenVPN and SWEET32 The default encryption for the transport protocol of OpenVPN is Blowfish – a 64-bit cipher – with the CBC mode. Meaning, the default encryption of OpenVPN prior to version 2.4 is BF-CBC which doesn't provide enough security in recent times. Newer versions of OpenVPN though are using AES-256-CBC as default cipher. Upgrade your cipher suite and block size For your own sake and safety of your network(s) you should check and change your OpenVPN infrastructure right away, and if needed upgrade your defined cipher to a more secure encryption and larger block size. OpenVPN users can change the cipher from the default Blowfish to AES First, check which ciphers are available on your server and clients using the --show-ciphers option like so: $ sudo openvpn --show-ciphers The following ciphers and cipher modes are available for use with OpenVPN. Each cipher shown below may be used as a parameter to the --cipher option. The default key size is shown as well as whether or not it can be changed with the --keysize directive. Using a CBC mode is recommended. DES-CBC 64 bit default key (fixed) RC2-CBC 128 bit default key (variable) DES-EDE-CBC 128 bit default key (fixed) DES-EDE3-CBC 192 bit default key (fixed) DESX-CBC 192 bit default key (fixed) BF-CBC 128 bit default key (variable) RC2-40-CBC 40 bit default key (variable) CAST5-CBC 128 bit default key (variable) RC2-64-CBC 64 bit default key (variable) AES-128-CBC 128 bit default key (fixed) AES-192-CBC 192 bit default key (fixed) AES-256-CBC 256 bit default key (fixed) CAMELLIA-128-CBC 128 bit default key (fixed) CAMELLIA-192-CBC 192 bit default key (fixed) CAMELLIA-256-CBC 256 bit default key (fixed) SEED-CBC 128 bit default key (fixed) Depending on your underlying Linux system the list might be more or less exhaustive. Have a look and then choose a key length of at least 128 bit. OpenVPN currently recommends using AES-256-CBC or AES-128-CBC. Following the article on OpenVPN and SWEET32 I chose to use AES-256-CBC cipher suite for my existing infrastructure. This seems to give me the largest compatibility between OpenVPN installations on various clients, including Raspberry Pi. Change your OpenVPN configuration Independent of the OpenVPN version installed, you can specify the cipher directive in your configuration files - server and client likewise. Usually that directive is either not present or commented, meaning it uses the compiled default value. Change it to your needs like so: # Select a cryptographic cipher. # This config item must be copied to # the client config file as well. ;cipher BF-CBC # Blowfish (default) ;cipher AES-128-CBC # AES cipher AES-256-CBC This needs to be applied on the OpenVPN server first as well as on all OpenVPN clients. Save your configuration file and reload the new settings. $ sudo service openvpn reload Perhaps, you might like to publish your updated client configuration file(s) a bit earlier. With the newly set cipher any connecting client will be rejected now, if the cipher suites do not match. Monitor your syslog output on the OpenVPN server for that kind of entries: Aug 28 07:33:26 smtp ovpn-server[18351]: 1.2.3.4:47081 WARNING: 'cipher' is used inconsistently, local='cipher AES-256-CBC', remote='cipher BF-CBC' Aug 28 07:33:26 smtp ovpn-server[18351]: 1.2.3.4:47081 WARNING: 'keysize' is used inconsistently, local='keysize 256', remote='keysize 128' ... Aug 28 07:34:08 smtp ovpn-server[18351]: client/1.2.3.4:47081 Authenticate/Decrypt packet error: cipher final failed This way you are able to find out which clients are still running on the previous configuration and therefore would need a little bit of assistance. Other OpenVPN appliances Thanks to some of the clients of my company IOS Indian Ocean Software Ltd. it happens that I have to connect to their networks via VPN from time to time. Given the changed cipher of my own OpenVPN infrastructure I wanted to see what others are using. According to my own article Connecting Linux to WatchGuard Firebox SSL (OpenVPN client) one of the client configuration files reads like this: cipher AES-256-CBC Whereas for another client who is using a firewall from Sophos, the chosen cipher suite looks like this: cipher AES-128-CBC Well, looks like I'm in good company with my new option. Security is a process, not a state Again, lesson learned. Although running services on Linux is mainly about setting them up properly at the beginning, it surely doesn't mean to forget about them in the long run. Regular reviews and audits help to mitigate newer issues and threats to your network infrastructure. If you are an active OpenVPN user please use the comment section to share other security related configuration settings and hardening tips on OpenVPN. That would be much appreciated by myself and other readers. Thanks! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'openvpn-revisited.html'; this.page.identifier = '9271ed1c97_idopenvpn-revisited'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "openvpn-via-windows-task-scheduler.html": {
    "href": "openvpn-via-windows-task-scheduler.html",
    "title": "Using OpenVPN connections via Windows Task Scheduler - Get Blogged by JoKi",
    "summary": "Using OpenVPN connections via Windows Task Scheduler Copy Markdown View Markdown GitHub Markdown Source Using OpenVPN connections via Windows Task Scheduler Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'openvpn-via-windows-task-scheduler'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'openvpn-via-windows-task-scheduler'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Using OpenVPN connections via Windows Task Scheduler') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-06-28 - Filed under WindowsProjects (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); There might be scenarios where you need a VPN connection on demand and for a limited time only. What else than using an OpenVPN connection which is triggered by a scheduled task in the Windows Task Scheduler? It seems to be the most obvious approach to handle this. No? Well, let me give you a little bit context on the actual requirement and what kind of unexpected obstacles came around the corner. The Use case aka Requirement In one of my project assignments the customer came up with a request similar to this one: \"We would like to backup the content from our Windows web server to NAS systems located in our various clients' intranets. This task shall be running on a daily schedule and the connection should only be established for the purpose of backup. For security reasons it shall run as a non-privileged user only.\" Sounds not much of a challenge and feasible, right? In principle, yes. The problem lies, as usual, in the details. The Approach During an initial brain-storming session with my customer we threw a few ideas around and finally decided that creating a temporary VPN connection between the web server and the intranet of each client might be a robust and secure approach. This combined with the use of rsync to synchronise the actual data over the wire. Hang on, you might argue now. Rsync could be used directly. And rsync has support for Secure Shell (SSH) which could provide the connection. Well, in general, I'd agree with you. However there had been a few additional constraints by the NAS system which unfortunately, although offering rsync and SSH capabilities, doesn't allow to use them together. Meaning the rsync transfer to the NAS would have to use the native protocol; which is non-encrypted and therefore insecure across public internet. Enabling SSH would allow us to connect to the NAS in an encrypted fashion however due to the lack of SSH forwarding capabilities in the NAS configuration the data transfer went nowhere. To solve this riddle we decided to stick with plain rsync but operating through a secure VPN tunnel instead of an SSH tunnel. The NAS system provides both features as built-in applications and we were ready to go. Defining the scheduled task in Windows Given previous experience creating a task in the Windows Task Scheduler here is a quick run-down of how I defined it. We are using PowerShell for the actual handling of the OpenVPN connection and the rsync data transfer. The general attributes of the scheduled are task are mainly default. Except that the user account used to run the task has been adjusted to a non-privileged user account and is different to the currently logged in user account. Quick check to show that the selected user account, here \"MSCC\", is a non-privileged one but assigned to administrative OpenVPN features. Next, the Action(s) for that scheduled task is to launch an instance of PowerShell (Core) and execute a PowerShell script to open and establish an OpenVPN connection, run the rsync command to synchronise data between source and target systems, and then terminate the connection at the end. Literally nothing fancy or extra-ordinary about the actual task scheduling. Kind of business as usual on a Windows operating system. Running the PowerShell script The following PowerShell script is the result of a few iterations. It was initially created by me and then extended successively based on the feedback and inputs by Markus Winhard and Silva Nair. # Name of your *.ovpn file. Please change. $OVPN_CNN = \"MyConnection\" # IP address of the device you want to acceess thru the VPN connection. Please change. $TARGET_IP = \"192.168.1.195\" $EXIT_EVENT_NAME = \"OVPN_\" + $PID $OVPN_CONFIG_DIR = $env:USERPROFILE + \"\\OpenVPN\\config\" $OVPN_CONFIG_FILE = $OVPN_CONFIG_DIR + \"\\\" + $OVPN_CNN + \".ovpn\" $OVPN_LOG_FILE = $env:USERPROFILE + \"\\OpenVPN\\log\\\" + $OVPN_CNN + \".log\" function Send-NamedPipeMessage($Message) { #$PipeName_OpenVPN3 = \"ovpnagent\" $PipeName = \"openvpn\\service\" $ComputerName = \".\" [System.Text.Encoding]$Encoding = [System.Text.Encoding]::Unicode [int]$ConnectTimeout = 5000 $stream = New-Object -TypeName System.IO.Pipes.NamedPipeClientStream -ArgumentList ` $ComputerName, ` $PipeName, ` ([System.IO.Pipes.PipeDirection]::Out), ` ([System.IO.Pipes.PipeOptions]::None), ` ([System.Security.Principal.TokenImpersonationLevel]::Impersonation) $stream.Connect($ConnectTimeout) $bRequest = $Encoding.GetBytes($Message) $stream.Write($bRequest, 0, $bRequest.Length) $stream.Dispose() } function Fire-ExitEvent($EventName) { $ev = [System.Threading.EventWaitHandle]::OpenExisting($EventName) # Next line always returning True. # Even when the connection was not closed. $ev.set() | Out-Null $ev.reset() | Out-Null $ev.close() | Out-Null } $Message = $OVPN_CONFIG_DIR + \"`0\" $Message = $Message + ` \"--log \" + $OVPN_LOG_FILE + ` \" --config \" + $OVPN_CONFIG_FILE + ` \" --service \" + $EXIT_EVENT_NAME + \" 0\" + ` \" --pull-filter ignore route-method\" + \"`0\" $Message = $Message + \"`0\" # Start VPN connection. Send-NamedPipeMessage $Message Start-Sleep 10 # VPN connection established. Do something. ping -n 1 $TARGET_IP | Select -Skip 1 -First 2 \"Do something here...\" # Close VPN connection. Fire-ExitEvent $EXIT_EVENT_NAME Start-Sleep 2 # Run ping command again to proof that the connection is closed. ping -n 1 $TARGET_IP | Select -Skip 1 -First 2 PowerShell script to establish and terminate an OpenVPN connection on demand Note: The name of the pipe is specific to OpenVPN version 2.x. It is different for OpenVPN version 3.x as described in the sources. The \"Do something\" section above would be the actual rsync command(s) to initiate the data synchronisation between the two source and target systems. For simplicity it pings the target only. Details on that one coming in a separate article. You can use the PowerShell script as a template for any kind of activities though. The Problem As mentioned earlier, we were armed and ready to do some backup magic, however OpenVPN for Windows had different ideas. Well, to be fair not completely different but at least covering the essential part that we needed: running as a scheduled task. In general, it would be an \"easy\" finger exercise on a Linux-based system using crond and an appropriate cron job to launch a bash script with the required user permissions. Nothing special on that part, unfortunately not so trying to do same on a Windows system. That wouldn't work yet. While inspecting the Windows Eventlog I came across two relevant entries. openvpnserv.exe 2.6.4.0 645cb955 ucrtbase.dll 10.0.19041.789 2bd748bf c0000409 0000000000071208 5b80 01d9889f8691751c C:\\Program Files\\OpenVPN\\bin\\openvpnserv.exe C:\\WINDOWS\\System32\\ucrtbase.dll 59fd8457-582d-48dd-87fe-568a9135a1cb The OpenVPN Interactive Service (openvpnserv.exe) logs an error for non-privileged user account And the memory dump produced by ucrtbase.dll. 1312317955627784568 5 BEX64 Not available 0 openvpnserv.exe 2.6.4.0 645cb955 ucrtbase.dll 10.0.19041.789 2bd748bf 0000000000071208 c0000409 0000000000000005 \\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WERD208.tmp.dmp \\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WERD266.tmp.WERInternalMetadata.xml \\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WERD277.tmp.xml \\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WERD285.tmp.csv \\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\Temp\\WERD2B4.tmp.txt \\\\?\\C:\\ProgramData\\Microsoft\\Windows\\WER\\ReportArchive\\AppCrash_openvpnserv.exe_6dc84221ee8158a35483d694e071e4a6628f97_5a017d55_9aff1096-886b-4e64-8093-ca3f3491e932 0 59fd8457-582d-48dd-87fe-568a9135a1cb 268435456 2553f2508d0a614952364a3a498f9978 0 Reference to a Windows Error Report (WER) / memory dump created by ucrtbase.dll Both errors are not logged while running the task with privileged credentials. So, the root cause has to be somewhere within the openvpnserv.exe after all. The Research An obvious approach was to practice a little Google-Fu (or Bing-Fu) and go through the various search results. Most of the time the results linked to either Stack Overflow, to its sister site Super User (StackExchange), or to a few private blog articles. Even the official OpenVPN site seems to have some content on that topic. However... Either the discussions are all about starting an OpenVPN connection automatically on logging into the Windows system (most results), running permanent automatically started connections (the remaining majority), or it simply states it cannot be done (a few ones). Everything was pointing to the requirement of an interactive user session, meaning a logged in user, in order to be able to establish the OpenVPN connection per task scheduler. Unfortunately using a permanent VPN tunnel automatically created by the OpenVPN service was not an option either. It had to be on demand. Because the Windows web server is running some multi-tenant applications and over time more clients would like to have their data backed up to their own NAS systems or any other private storage resources. For security and isolation reasons we cannot run multiple OpenVPN connections at the same time. Imagine the CIDR related troubles we might be facing. Clearly that is not covering our initial requirement. The Windows web server can be rebooted any time as needed and there is surely no user constantly logged into the system after all. Plus, keeping RDP constantly open is a security-related concern. So, what to do? The Solution I started from the OpenVPN wiki page OpenVPN Interactive Service Notes and tried to communicate with the named pipe provided by the service directly instead of relying on the use of either openvpn.exe or openvpn-gui.exe. It took me a bit to translate the message preparation as well as the exit event handling from the C source code samples to PowerShell using .NET syntax. Nonetheless, I was able to create a new PowerShell based client for the OpenVPN Interactive Service and I managed to establish a connection this way. However there were two main issues with it. a) I couldn't terminate the connection after the rsync data transfer had completed, hence I had to work with the rather harsh taskkill.exe command to kill the process. And b) it still wouldn't run in a logged-out user session using a non-privileged user account. Luckily, OpenVPN is open source and publicly hosted on GitHub. And for that same reason we opened an issue in the GitHub repository, providing as much details and logged information plus all the cases unsuccessfully tested as possible. OpenVPN GUI not connecting when started by Windows Task Scheduler #626 As you might see in the thread of comments and details exchanged it turned out the openvpn.exe application being the root cause of the problem. The application is used by the OpenVPN Interactive Service on a Windows system which provides a non-privileged user access to privileged resources like setting the routes and others. Looking at the actual resulting change of source code is even more fascinating. You should have a look at the commit 57aceb7 signed of by Silva Nair: Interactive service: do not set target desktop for openvpn.exe process yourself. Fascinating,or? And the rest is history, so to speak. As of OpenVPN version 2.6.5 - see ChangeLog - it is now possible to run scripted and therefore scheduled tasks on Windows using a non-privileged user account to establish an OpenVPN connection. - interactive service (windows): do not force target desktop for openvpn.exe - this has no impact for normal use, but enables running of OpenVPN in a scripted way when no user is logged on (for example, via task scheduler) (Github OpenVPN/openvpn-gui#626) Kudos to the OpenVPN people A big Thank You to the developers of OpenVPN, in particular to Silva Nair and Gert Doering, for the patience with our concerns and provided logs, the understanding of the underlying problem, and the requirements to get this resolved, and of course the timely provided solution to finally enable scheduled, on-demand OpenVPN connections on Windows using a non-privileged user account. Thanks to my long-time friend Markus Winhard for the initial task assignment, his patience and perseverance to figure out the root cause together with me. FYI, the results of the scheduled data syncs / backups on the production system are beyond expectations. Approximately 240 GB are fully rsync'd in less than 15 minutes. Totally worth the extra time and effort. Image has been generated by Bing using the following prompt: \"Create an image depicting OpenVPN on a Windows Server 2022 system and an encrypted tunnel for data transfer.\" if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'openvpn-via-windows-task-scheduler.html'; this.page.identifier = '9271ed1c97_idopenvpn-via-windows-task-scheduler'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "orange-mauritius-smtp-auth.html": {
    "href": "orange-mauritius-smtp-auth.html",
    "title": "Some mail details about Orange Mauritius - Get Blogged by JoKi",
    "summary": "Some mail details about Orange Mauritius Copy Markdown View Markdown GitHub Markdown Source Some mail details about Orange Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'orange-mauritius-smtp-auth'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'orange-mauritius-smtp-auth'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Some mail details about Orange Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2011-02-11 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Being an internet service provider is not easy after all for a lot of companies. Luckily, there are quite some good international operators in this world. For example Orange Mauritius aka Mauritius Telecom aka Wanadoo(?) aka MyT here in Mauritius. The local circumstances give them a quasi-monopol position on fixed lines for telephony and therefore cable-based DSL internet connectivity. So far, not bad but as usual... the details. Just for the records, I am only using the services of Orange for mobile but friends and customers are bound, eh stuck, with other services of Orange Mauritius. And usually, being the IT guy, they get in touch with me to complain about problems or to ask questions on either their ADSL / MyT connection, mail services or whatever. Most of those issues are user-related and easily to solve by tweaking the configuration of their computer a little bit but sometimes it's getting weird. Using Orange ADSL... somewhere else Now, let's imagine we are an Orange ADSL customer for ages and we are using their mail services with our very own mail address like \"ficticious@intnet.mu\". We configured our mail client like Thunderbird, Outlook Express, Outlook or Windows Mail as publicly described, and we are able to receive and send emails like a champion. No problems at all, the world is green. Did I mention that we have a laptop? Ok, let's take our movable piece of information technology and visit a friend here on the island. Not surprising, he is also customer of Orange, so we can read and answer emails. But Orange is not the online internet service provider and one day, we happen to hang out with someone that uses Emtel via WiMAX or UMTS.. And the fun starts... We can still receive and read emails from our Orange mail account and the IT world is still bright but try to send mails to someone outside the domain \"@intnet.mu\" or \"@orange.mu\". Your mail client will deny sending mail with SMTP message 5.1.0 \"blah not allowed\". First guess, there is problem with the mail client, maybe magically the configuration changed over-night. But no it is still working at home... So, there is for sure a problem with the guy's internet connection. At least, it is his fault not to have Orange internet services, so it can not work properly... The Orange Mail FAQ After some more frustation we finally checkout the Orange Mail FAQ to see whether this (obviously?) common problem has been described already. Sorry, but those FAQ entries are even more confusing as it is not really clear how to handle this scenario. Best of all is that most of the entries are still refering to use servers of the domain \"intnet.mu\". I mean Orange will disable those systems in favour of the domain \"orange.mu\" in the near future and does not amend their FAQs. Come on, guys! Ok, settings for POP3 are there. Hm, what about the secure version POP3S? No signs at all... Even changing your mail client to use password encryption with STARTTLS is not allowed at all. Use \"bow.intnet.mu\" for incoming mail... Ahhh, pretty obvious host name. I mean, at least something like pop.intnet.mu or pop3.intnet.mu would have been more accurate. Funny of all, the hostname \"pop.orange.mu\" is accessible to receive your mail account. Alright, checking SMTP options for authentication or other like POP-before-SMTP or whatever well-known and established mechanism to send emails are described. I guess that spotting a whale or shark in Mauritian waters would be easier. Trial and error on SMTP settings reveal that neither STARTTLS or any other connection / password encryption is available. Using SSL/TLS on SMTP only reveals that there is no service answering your request. Calling customer service So, we have to bite into the bitter apple and get in touch with Orange customer service and complain/explain them our case and ask for advice. After some hiccups, we finally manage to get hold of someone competent in mail services and we receive the golden spoon of mail configuration made by Orange Mauritius: SMTP hostname: smtpauth.intnet.mu And the world of IT is surprisingly green again. Customer satisfaction? Dear Orange Mauritius, what's the problem with this information? Are you scared of mail spammer? Why isn't there any case in your FAQs? Ok, talking about your FAQs - simply said: they are badly outdated! Configure your mail client to use server name based in the domain intnet.mu but specify your account username with orange.mu as domain part. Although, that there are servers available on the domain orange.mu after all. So, why don't you provide current information like this: POP3 server name: pop.orange.mu SMTP server name: smtp.orange.mu SMTP authenticated: smtpauth.orange.mu It's not difficult, is it? In my humble opinion not really and you would provide clean, consistent and up-to-date information for your customers. This would produce less frustation and so less traffic on your customer service lines. Which after all, would improve the total user experience and satisfaction level on both sides. Without knowing these facts. Now, imagine you would take your laptop abroad and have to use other internet service providers to be able to be online... Calling your customer service would be unnecessary expensive! Image credit: Kaitlyn Chow if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'orange-mauritius-smtp-auth.html'; this.page.identifier = '9271ed1c97_idorange-mauritius-smtp-auth'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "oreilly-verffentlicht-rezension.html": {
    "href": "oreilly-verffentlicht-rezension.html",
    "title": "O'Reilly veröffentlicht Rezension - Get Blogged by JoKi",
    "summary": "O'Reilly veröffentlicht Rezension Copy Markdown View Markdown GitHub Markdown Source O'Reilly veröffentlicht Rezension Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'oreilly-verffentlicht-rezension'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'oreilly-verffentlicht-rezension'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'O'Reilly veröffentlicht Rezension') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-12 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Wow, ich bin begeistert. O'Reilly hat tatsächlich Auszüge aus meiner Rezension zum englischsprachigen Titel C# Essentials 2nd Edition auf ihren Produktseiten veröffentlicht: https://www.oreilly.de/catalog/csharpess2/reviews.html Es ist wirklich angenehm den eigenen Text in den Weiten des Webs zu entdecken. Und hiermit möchte ich mich nochmals bei O'Reilly bedanken, dass sie es Usergroups ermöglichen, sich so intensiv mit ihren Titeln zu beschäftigen. Vielen herzlichen Dank. Die nächsten Buch-Kommentare sind bereits in der Mache und werden voraussichtlich auf Amazon veröffentlicht. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'oreilly-verffentlicht-rezension.html'; this.page.identifier = '9271ed1c97_idoreilly-verffentlicht-rezension'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "passed-exam-70-480-programming-in-html5-with-javascript-and-css3.html": {
    "href": "passed-exam-70-480-programming-in-html5-with-javascript-and-css3.html",
    "title": "Passed: Exam 70-480: Programming in HTML5 with JavaScript and CSS3 - Get Blogged by JoKi",
    "summary": "Passed: Exam 70-480: Programming in HTML5 with JavaScript and CSS3 Copy Markdown View Markdown GitHub Markdown Source Passed: Exam 70-480: Programming in HTML5 with JavaScript and CSS3 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'passed-exam-70-480-programming-in-html5-with-javascript-and-css3'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'passed-exam-70-480-programming-in-html5-with-javascript-and-css3'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Passed: Exam 70-480: Programming in HTML5 with JavaScript and CSS3') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-12-18 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); First off: Mission accomplished successfully. And it was fun! Using the resources listed in my previous article about Learning Content, I'd like to thank Microsoft Technical Evangelists Jeremy Foster and Michael Palermo for their excellent jump start videos on Channel 9, and the various authors at Pluralsight. Local Prometric testing centre Back in November I chose a local testing centre which was the easiest to access from my office despite the horrible traffic you might experience here on the island. Actually, it was not the closest one. But due to their website, their awards as Microsoft Learning Center, and my general curiosity about the premises, I gave FRCI my priority. Boy, how should I regret this decision this morning... The official Prometric exam guide asks any attendee to show up at least 30 minutes prior to the scheduled time of the test. Well, this should have been the easier part but unfortunately due to heavier traffic than usual I arrived only 20 minutes before time. Not too bad but more to come. The building called 'le Hub' is nicely renovated and provides the right environment for an IT group of companies like FRCI. I think they have currently 5 independent IT departments over there. Even the handling at the reception was straight forward, welcoming and at my ease. But then... first shock: \"We don't have any exam registration for today.\" - Hm, that's nice... Here's my mail confirmation from Prometric. First attack successfully handled and the lady went off again to check their records. Next shock: A couple of minutes later, another guy tries to explain me that \"the staff of the testing centre is already on vacation and the centre is officially closed.\" - Are you kidding me? Here's the official confirmation by Prometric, and I don't find it funny that I take a day off today only to hear this kind of blubbering nonsense. I thought that I'll be on the safe side choosing a company with a good reputation here on the island. Another 40 (!) minutes later, they finally come back to the waiting area with a pre-filled form about the test appointment. And finally, after an hour of waiting, discussing, restarting the testing PC, and lots of talk, I am allowed to sit down and take the exam. Exam details Well, you know the rules. Signing an NDA doesn't allow me to provide you any details about the questions or topics that have been covered. Please check out the official exam description, and you're on the right way. Sorry, guys... \uD83D\uDE09 The result \"Congratulations! You have passed this Microsoft Certification exam.\" - In general, I have to admit that the parts on HTML5 and CSS3 were the easiest after all, and that I have to get myself a little bit more familiar with certain Javascript features like class definitions, inheritance and data security. Anyway, exam passed - who cares about the details? Next goal Of course, the journey to Microsoft Certifications continues and my next goal is to pass exams 70-481 - Essentials of Developing Windows Store Apps using HTML5 and JavaScript and 70-482 - Advanced Windows Store App Development using HTML5 and JavaScript. This would allow me to achieve the certification of MCSD: Windows Store Apps using HTML5. I guess, during 2013 I'll be busy with various learning and teaching lessons. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'passed-exam-70-480-programming-in-html5-with-javascript-and-css3.html'; this.page.identifier = '9271ed1c97_idpassed-exam-70-480-programming-in-html5-with-javascript-and-css3'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "pathinfo-oder-wozu-gibt-es-eigentlich-rfcs.html": {
    "href": "pathinfo-oder-wozu-gibt-es-eigentlich-rfcs.html",
    "title": "PATH_INFO oder wozu gibt es eigentlich RFCs? - Get Blogged by JoKi",
    "summary": "PATH_INFO oder wozu gibt es eigentlich RFCs? Copy Markdown View Markdown GitHub Markdown Source PATH_INFO oder wozu gibt es eigentlich RFCs? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pathinfo-oder-wozu-gibt-es-eigentlich-rfcs'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pathinfo-oder-wozu-gibt-es-eigentlich-rfcs'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'PATH_INFO oder wozu gibt es eigentlich RFCs?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-10 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); An manchen Tagen sollte man erst gar nicht mit dem Programmieren anfangen und einfach im Bett liegen bleiben. So auch heute. Nach etlichen Jahren holte mich heute aus ganz konspirativen Gründen die Vergangenheit ein. Da ich bereits seit 1996 dynamische Webseiten entwickle, kommt potentiell einiges an Wissen und Informationen - engl. Knowledge - zu bestimmten Themen im Bereich der Webentwicklung zusammen. Und eines hat mich dabei bestimmt mehr als einmal interessiert: Servervariablen Sind extrem nützlich beim Schreiben von dynamischen Sites, die beispielsweise mit relativen Pfadangaben, Informationen zum Request und zum eigentlichen Webserver arbeiten sollen / müssen. Dabei bin ich immer mal wieder über die PATH_INFO Variable gestolpert. Ich habe mich ehrlich gesagt nie sonderlich intensiv mit dieser speziellen Variablen beschäftigt, bis zum heutigen Tage... PATH_INFO allgemein Ehrlich gesagt, fehlt mir aktuell die Information, was der eigentliche Auslöser war, dass ich mir diese Variable intensiver angeschaut habe. Ich würde aktuell mal drauf tippen, dass es mit dem DirectCall Plugin der Active FoxPro Pages begonnen hat. Das DirectCall ermöglicht es unter anderem, dass man die Objekte und Methoden seiner Webanwendung \"direkt\" aufrufen kann. Daher auch die Namensgebung. Nun, die zu verwendende URL sieht dabei ungefähr so aus: https://localhost/!application/object/method.afp Entscheidend hierbei ist das Ausrufezeichen zu Beginn des Skriptpfades. Und dabei kam die Idee, dass man ja prinzipiell auch Pfadinformationen nach der Skriptdatei nutzen kann. Das Ganze sieht dann exemplarisch so aus: https://localhost/script.afp/weiterer/Pfad/irgendwohin In beiden Fällen sind es gültige URLs. Wieso eigentlich der Aufwand? Nun, die Idee hinter diesem URL-Konstrukt ist, dass man suchmaschinenfreundliche Adressen produzieren kann ohne auf zusätzliche, serverspezifische Module wie mod_rewrite zurück greifen zu müssen. Denn... und nun kommt der Trick an der Sache: Genau dafür gibt es die Servervariable PATH_INFO. Zumindest in der Theorie... PATH_INFO - Ist-Zustand Die Praxis bringt einen ganz schnell wieder auf den Boden der Tatsachen, denn weder der IIS noch der Apache über mod_isapi bringen die korrekten Werte für die Variable. Mein Problem hat damit begonnen, dass der obige Aufruf mit zusätzlichem Pfad nach der Skriptdatei in Active FoxPro Pages zu einem HTTP 404 führte. Okay, wir haben ja Sourcen und können uns das mal anschauen... Nach einem einstündigem Skype-Talk mit meinem Kollegen Christof kamen wir dann auf den Punkt, dass ich ein AFP Plugin schreibe und mal genauer analysiere, was denn tatsächlich vom Webserver in PATH_INFO und daran gekoppelt PATH_TRANSLATED geschickt wird. hüstel - Ich hätte es besser nicht machen sollen... Denn beide Server bringen, ehrlich gesagt, nicht den erwarteten Wert. PATH_INFO - Soll-Zustand Nach ein paar Recherchen und Durchforsten von Bugreports im Bereich Perl und PHP, dann die Analyse der RFC 3875 - The Common Gateway Interface (CGI) Version 1.1 und joah, es wird immer interessanter. Laut RFC 3875 definiert sich PATH_INFO folgendermaßen (Kapitel 4.1.5): The PATH_INFO variable specifies a path to be interpreted by the CGI script. It identifies the resource or sub-resource to be returned by the CGI script, and is derived from the portion of the URI path hierarchy following the part that identifies the script itself. Und gemäß der Darstellung in Kapitel 3.3 - The Script-URI: The various components of the Script-URI are defined by some of the meta-variables (see below); script-URI = <scheme> \"://\" <server-name> \":\" <server-port> <script-path> <extra-path> \"?\" <query-string> where <scheme> is found from SERVER_PROTOCOL, <server-name>, <server-port> and <query-string> are the values of the respective meta-variables. The SCRIPT_NAME and PATH_INFO values, URL-encoded with \";\", \"=\" and \"?\" reserved, give <script-path> and <extra-path>. würde dies nach RFC bedeuten, dass im Normalfall PATH_INFO und damit verbunden PATH_TRANSLATED leer sind. Erst bei so obstrusen URLs wie etwa https://localhost/script.afp/weiterer/Pfad/irgendwohin würde sich die Servervariable mit dem Wert /weiterer/Pfad/irgendwohin füllen und dann wiederum vom Webserver durch die virtual-to-physical mappings auf einen korrespondierenden Wert in PATH_TRANSLATED erweitert werden. Tja, leider ist dem nicht so. Und ich glaube, dass bei der weiteren intensiven Beschäftigung mit der RFC 3875 weitere Ungereimtheiten ans Tageslicht kommen würden. Sowohl traurig wie auch spannend finde ich jedoch, dass selbst der Apache über Jahre nun diese Variable verkehrt füllt. **Konsequenzen... ** Nun, wenn es die Webserver nicht einheitlich korrekt gemäß RFC hinbekommen, dann gibt's nur zwei Möglichkeiten - die Segel streichen oder was dagegen unternehmen. Ich werde auf alle Fälle das AFP Plugin programmieren, da mich zum einen die suchmaschinenfreundlichen URLs ohne mod_rewrite interessieren und zum anderen weil es nach RFC konform wäre, und damit weitere Tricks im Zusammenspiel mit der Entwicklung von dynamischen Websites bietet. Und ich werde einen Bugreport für den IIS 7.0 einreichen. \uD83D\uDE09 Schliesslich bin ich seit längerem Beta Tester für Windows Vista und Longhorn Server, also kann man dazu auch mal was Passendes melden. PathInfo Plugin Netterweise kann man in den Active FoxPro Pages sehr einfach und schnell, neue Plugins einbinden und verwenden. Persönlich halte ich die Implementierung und Befüllung seitens der Webserver für nicht RFC-konform und damit leiden natürlich dann auch die Skriptsprachen, welche genutzt werden - egal, ob AFP, PHP, ASP.NET oder Sonstiges. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'pathinfo-oder-wozu-gibt-es-eigentlich-rfcs.html'; this.page.identifier = '9271ed1c97_idpathinfo-oder-wozu-gibt-es-eigentlich-rfcs'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "pathinfo-plugin-fr-afp.html": {
    "href": "pathinfo-plugin-fr-afp.html",
    "title": "PathInfo Plugin für AFP - Get Blogged by JoKi",
    "summary": "PathInfo Plugin für AFP Copy Markdown View Markdown GitHub Markdown Source PathInfo Plugin für AFP Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pathinfo-plugin-fr-afp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pathinfo-plugin-fr-afp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'PathInfo Plugin für AFP') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-11 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nachschlag... Ausgehend von meinem gestrigen Eintrag heute eine kleinere Ergänzung. Da mich die Thematik doch ein wenig heute Nacht beschäftigt hat, habe ich mal hingesetzt und ein Plugin für die Active FoxPro Pages programmiert. Denn, so wie es nach allem Anschein aussieht, scheint es sich hierbei um eine Limitierung seitens der ISAPI im Vergleich zu reellem CGI 1.1 zu sein. Laut Aussage in der MSDN zu ISAPI-Extension findet sich nämlich folgendes: Path information, as given by the client, for example, \"/vdir/myisapi.dll/zip\". If this information comes from a URL, it is decoded by the server before it is passed to the CGI script or ISAPI filter. If the AllowPathInfoForScriptMappings metabase property is set to true (to support exclusive CGI functionality), PATH_INFO will only contain \"/zip\" and ISAPI applications such as ASP will break. -- Quelle: MSDN Ohne Sichtung des Quellcode vom Apache mod_isapi gehe ich davon aus, dass es sich hierbei um das gleiche Verhalten handelt, nämlich dass eben ISAPIs nicht fehlschlagen. Da die AFP derzeit eben auch als ISAPI programmiert ist, wird mir wohl nichts anderes übrig bleiben, als die Situation anders zu lösen. Und dabei gibt es wiederum mehrere Lösungsmöglichkeiten; ich habe mich aktuell für die einfachste entschieden: Plugin erstellen. Gemäß den Vorgaben der AFP-Hilfe zur Puginerstellung geht das auch sehr schnell und einfach über die Tastatur. Kleine Orientierung im mitgelieferten Quellcode vom Voodoo Plugin und dem Beispielcode für das RuntimeExec Plugin und bunt zusammengewürfelt. Nach ein, zwei Stunden inklusive der Tests unter Apache und IIS kam dann folgendes dabei raus: Code: *====================================================================== Executes AFP pages based on current PATH_INFO information of web server. This plugin checks the URL and re-routes page execution based of the provided path information. This behaviour is related to AcceptPathInfo directive of Apache web server and AllowPathInfoForScriptMappings of IIS. MSDN information: ms-help://MS.MSDNQTR.2006JAN.1033/iissdk/html/da14bb52-fb04-4e0b-a11b-a6035b5d65c9.htm This plugin parses PATH_INFO and PATH_TRANSLATED as described in RFC 3875: http://www.ietf.org/rfc/rfc3875 *====================================================================== LParameter roPlugIn roPlugIn = CreateObject(\"CPlugIn_PathInfo\") *====================================================================== CPlugIn_PathInfo *====================================================================== Define Class CPlugIn_PathInfo as CPlugin ; & nbsp;cID = \"PathInfo\" oHook = NULL Procedure Load This.oHook = CreateObject(\"CHookPathInfo\") This.oHook.Hook&# 40;) EndProc Procedure Unload This.oHook.Unhook() This.oHook.Release() EndProc EndDefine *====================================================================== CHookCallFactory objects are responsible to parse the request and return an appropriate CCall object, if they want to handle a request. *====================================================================== Define Class CHookPathInfo as CHookCallFactory Procedure CreateCall LParameter toFile, tnLevel &nb; sp; Local lcApp, lcPage *------------------------------------------------------------------ Get references to various AFP objects and store them in private variables. The CreateObject() method only creates a new object, if the requested object has not yet been loaded. Otherwise, a reference to the existing object is returned. NEVER store these references in properties. This might cause problems with AFP. *------------------------------------------------------------------ &nbs; p;Private REQUEST REQUEST = This.CreateObject(\"Request\") Local lcPathInfo, lcPathTranslated, lcLocation, lcVirtualLocation m.lcPathInfo = Request.ServerVariables(\"PATH_INFO\") m.lcPathTranslated = Request.ServerVariables(\"PATH_TRANSLATED\") ; & nbsp; m.lcPathInfo = StrExtract(m.lcPathInfo,\".afp\",\"\",1,1+2) m.lcLocation = Strtran(m.lcPathTranslated, Chrtran(m.lcPathInfo, \"/\", \"\"), \"\") &n; bsp; m.lcVirtualLocation = Strtran(toFile.cVirtualLocation, m.lcPathInfo, \"\") m.lcPathTranslated = JustPath(m.lcLocation) + Chrtran(m.lcPathInfo, \"/\", \"\") If Empty(m.lcPathInfo) ; & nbsp; m.lcPathTranslated = \"\" EndIf *------------------------------------------------------------------ Correct PATH_* server variables and execute requested script file. *------------------------------------------------------------------ Request.cData = Strtran( ; Request.cData, ; [PATH_INFO:] + Request.ServerVariables(\"PATH_INFO\"), ; [PATH_INFO:] + m.lcPathInfo, ; 1, 1, 2 ; ) Request.cData = Strtran( ; Request.cData, ; [PATH_TRANSLATED:] + Request.ServerVariables(\"PATH_TRANSLATED\"), ; [PATH_TRANSLATED:] + m.lcPathTranslated, ; 1, 1, 2 ; ) Request.Reset() toFile.Reset( ; m.lcLocation, ; m.lcVirtualLocation, ; toFile.cHost ; ) Return DoDefault(m.toFile,m.tnLevel) EndProc EndDefine Die Klasse CPlugIn_PathInfo ist die konkretisierte Ableitung der AFP Pluginklasse und darin instanziieren wir die eigentliche Anpassung der ServerVariablen in der Klasse CHookPathInfo. Wir müssen wir über eine Factory gehen, da die Active FoxPro Pages intern eine Chain of Responsibility über alle Call-Prozesse aufbaut und auf diese Weise ermöglicht, dass multiple Plugins den ursprünglichen Request des Clients analysieren, modifizieren und verarbeiten können. Die Integration in die AFP erfolgt entweder per ControlCenter oder händisch über die Bearbeitung der afp.config: Code: <plugin location=\"%root%\\plugin\\pathinfo.fxp\" engine=\"AFP\"/> Zeile einfügen, speichern und AFP neu starten. Anbei übrigens auch ein ZIP-Archiv mit den benötigten Dateien: http://jochen.kirstaetter.name/files/joki_PathInfo.plugin.zip Das war's auch schon. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'pathinfo-plugin-fr-afp.html'; this.page.identifier = '9271ed1c97_idpathinfo-plugin-fr-afp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "penta-ws802c-the-white-orange-tablet.html": {
    "href": "penta-ws802c-the-white-orange-tablet.html",
    "title": "Pantel Penta WS802C (aka The white Orange tablet) - Get Blogged by JoKi",
    "summary": "Pantel Penta WS802C (aka The white Orange tablet) Copy Markdown View Markdown GitHub Markdown Source Pantel Penta WS802C (aka The white Orange tablet) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'penta-ws802c-the-white-orange-tablet'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'penta-ws802c-the-white-orange-tablet'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Pantel Penta WS802C (aka The white Orange tablet)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-05-22 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); {vsig}ws802c{/vsig} Already last year, Orange Mauritius introduced two affordable tablets (price tags: Rs. 2,999.- and 5,999.-) for every household at the Infotech 2012. So the vision at that time. Actually, I pre-ordered both tablets as they seem to be 'good value for money'... The Penta WS802C tablet is generally available since begin of this year, and eventually you might be interested in whether this tablet could be a good choice to enter the world of Android tablets. Pantel has the official specs on their website but don't get confused as there are actual two models. Orange sells the 3G version here in Mauritius. Based on the hardware and software the Penta tablet ranges in the entry-level. It comes with Android Ice Cream Sandwich (ICS version 4.0.3) and a couple of pre-installed apps, like Skype, Facebook, Twitter, Angry Birds and LinkedIn just to name a few. Even thought you can install additional apps from the Google Play Store, it seems that those cannot be updated. The Penta WS802C is classified as a Calling Device and in combination with a standard SIM card you can actually call other people with the telephone app. The tablet comes with 4GB memory built-in and the capacity can be extended with an additional MicroSD card of up to 32GB (highly recommended). Furthermore, it has a lot of ports to offer which allows to plug in USB peripherals and you can hook it up to your TV set via HDMI cable. Equipped with a Cortex A8 single core processor at 1.0GHz the overall performance could be better. The main disadvantages of the Penta WS802C are its weight, the battery life-time, the low screen resolution of 800 x 600 pixels, and the casing gets very hot at the backside. With its 570 grams you cannot hold this tablet for long to read a book or to surf the web for some hours. Which isn't possible anyways thanks to the surprisingly short battery life-time. The shortest period of intensive use timed in at roughly 3 hours starting with a fully-charged battery. Browsing the web is sufficient enough to read the latest news or check your mails. Overall the Penta WS802C is clearly a low-end device for the mass market. It provides a solid base for first experience with Android tablets at a very affordable price compared to other devices on the market. In our household the Penta is mainly used by the children in order to keep them away from the more expensive ones. They simply love it and I don't have to worry about accidental drops. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'penta-ws802c-the-white-orange-tablet.html'; this.page.identifier = '9271ed1c97_idpenta-ws802c-the-white-orange-tablet'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "php-web-services.html": {
    "href": "php-web-services.html",
    "title": "PHP Web Services by Lorna Jane Mitchell - Nice try - Get Blogged by JoKi",
    "summary": "PHP Web Services by Lorna Jane Mitchell - Nice try Copy Markdown View Markdown GitHub Markdown Source PHP Web Services by Lorna Jane Mitchell - Nice try Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'php-web-services'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'php-web-services'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'PHP Web Services by Lorna Jane Mitchell - Nice try') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-07-16 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Thanks to the membership in the O'Reilly User Group Programme the Mauritius Software Craftsmanship Community (short: MSCC) recently received a welcome package with several book titles. Among them is the latest publication of Lorna Jane Mitchell - 'PHP Web Services: APIs for the Modern Web'. Following is the book review I put on Amazon: Nice try! Initially, I was astonished that a small book like 'PHP Web Services' would be able to cover all the interesting topics about APIs and Web Services, independently whether they are written in PHP or not. And unfortunately, the title isn't able to stand up to the readers (or at least my) expectations. Maybe as a light defense, there is no usual paragraph about the intended audience of that book, but still I have to admit that the first half (chapters 1 to 8) are well written and Lorna has her points on the various technologies. Also, the code samples in PHP are clean and easy to understand. With chapter 'Debugging Web Services' the book started to change my mind about the clarity of advice and the instructions on designing and developing good APIs. Eventually, this might be related to the fact that I'm used to other tools since years, like Telerik Fiddler as HTTP proxy in order to trace and inspect any kind of request/response handling. Including localhost monitoring, SSL certification acceptance, and the ability to debug mobile devices, especially iOS-based ones. Compared to Charles, Fiddler is available for free. What really got me off the hook is the following statement in chapter 10 about Service Type Decisions: \"For users who have larger systems using technology stacks such as Java, C++, or .NET, it may be easier for them to integrate with a SOAP service.\" WHAT? A couple of pages earlier the author recommends to stay away from 'old-fashioned' API styles like SOAP (if possible). And on top of that I wonder why there are tons of documentation towards development of RESTful Web Services based on WebAPI. The ASP.NET stack clearly moves away from SOAP to JSON and REST since years! Honestly, as a software developer on the .NET stack this leaves a mixed feeling after all. As for the remaining chapters I simply consider them as 'blah blah' without any real value and lots of theoretical advice. Related to the chapter 13 about 'Documentation', I just had the 'pleasure' to write a C#-based client against a Java-based SOAP Web Service. Personally, I take the WSDL as the master reference in the first place and Visual Studio generates all the stub types involved in the communication. During the implementation and testing I came across a 'java.lang.NullPointerException' in various methods and for various method parameters. The WSDL and the generated types were declared as Nullable, so nothing to worry about, or? Well, I logged in a support ticket, and guess what was the response to that scenario? \"The service definition in the WSDL is wrong, please refer to the documentation in order to use the methods and parameters correctly\" - No comment! Lorna's title is a quick read and in some areas she has good advice on designing and implementing Web Services and APIs. But roughly 100 pages aren't enough to cover a vast topic like that. After all, nice try and I'm looking forward to an improved second edition. Honestly, I never thought that I would come across a poor review. In general, it's a good book but it clearly has a lack of depth, the PHP code samples are incomplete (closing tags missing), and there are too many assumptions and theoretical statements. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'php-web-services.html'; this.page.identifier = '9271ed1c97_idphp-web-services'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "pictures-wanna-pictures.html": {
    "href": "pictures-wanna-pictures.html",
    "title": "Pictures? Wanna pictures? - Get Blogged by JoKi",
    "summary": "Pictures? Wanna pictures? Copy Markdown View Markdown GitHub Markdown Source Pictures? Wanna pictures? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pictures-wanna-pictures'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pictures-wanna-pictures'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Pictures? Wanna pictures?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-18 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ja ja... Unfreiwillige Fotos machen echt keine Laune - vor allem nicht, wenn das Blitzlicht rot ist und man im Auto unterwegs ist. So ging's uns zumindest gestern Abend auf dem Weg zu einem Geburtstag. Nicht, dass wir in Eile gewesen wären. Nein, im Gegenteil! Wir unterhielten uns gemütlich und fuhren eine zweispurige Strasse mit genehmigten 70 km/h entlang. Nunja, irgendwie schien sich jemand den Spass zu machen, abends so, ca. 18:45 auf Grund einer Baustelle mit 50er Beschränkung Passbilder zu schiessen... Meiner Ansicht nach, handelt es dich dabei um willkürliche Abzocke! Aber wer weiß... vielleicht waren ja noch ein paar Schwarzarbeiter um diese Uhrzeit auf der Baustelle aktiv und mussten daher unbedingt vor den bösen Autofahrern geschützt werden. Achja, es sei noch gesagt, dass die Arbeiten an einem Brückenpfeiler, der geschätzte 8 bis 10 Meter entfernt von der Fahrbahn praktiziert werden. Nunja, Mannheim braucht Geld... Bis denne, JoKi PS: Ich bin nicht gefahren... if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'pictures-wanna-pictures.html'; this.page.identifier = '9271ed1c97_idpictures-wanna-pictures'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "plans-for-mauritius-software-developer-user-group-msdug.html": {
    "href": "plans-for-mauritius-software-developer-user-group-msdug.html",
    "title": "Plans for Mauritius Software Developer User Group (MSDUG) - Get Blogged by JoKi",
    "summary": "Plans for Mauritius Software Developer User Group (MSDUG) Copy Markdown View Markdown GitHub Markdown Source Plans for Mauritius Software Developer User Group (MSDUG) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'plans-for-mauritius-software-developer-user-group-msdug'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'plans-for-mauritius-software-developer-user-group-msdug'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Plans for Mauritius Software Developer User Group (MSDUG)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-03-04 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); You know... Old habits don't fall off! And so, I'm still interested in starting and operating (again) a local user group. This one is more in general about topics according to software development, design and data storage than my last ones back in Germany. Because I was maintainer of a regional user group of the German FoxPro User Group I'd like to use my experiences in this field to kick-off a new user group here in Mauritius. This idea is already since the very first beginning on my mind, but because of my work I didn't have the time yet to get more details and so on.... At the end of January, I already tried to set up such a new user group but because of cyclonic weather conditions we postponed the meeting. Now, I think it's really time for a second attempt to found the user group. Well, you might think: Not another one but actually first I did some research on existing user groups here in Mauritius and yes, there are some (.NET, Linux and probably for PHP) but the current situation is that they seem to be inactive or endagered. At last, I was not able to get recent information about meetings. And based on this situation, I'm enthuastic about running 'my own' user group for software engineers, developers in general and database related topics. The main focus will be development on the Windows operating systems - to be precise we are going to talk about Visual Studio, SQL Server, Office System and Visual FoxPro - but the topics are not restricted. People that know me, know that even at the dFPUG meetings we also talked about things than VFP. As you see in the subject of this article the name is already there: Mauritius Software Developer User Group (MSDUG) But for sure, I'd like to see way more than just a name, this article and an outdated homepage on the internet. The concepts are there but not fixed yet. Currently, I'm searching a nice location where we can meet regularly. The former user groups seem to have the DCDM in Quatre Bornes as there location. Well, this sounds good, lies central and is accessible from almost anywhere on the island by bus. But I don't like to share the same fate with the others. So, if any Mauritian is reading this article please drop me a mail or leave a comment. Thanks in advance. Later on this year, I'd like to promote the MSDUG on local events like job market fairs, events at the University of Technology in Reduit and other opportunities. We will see what the future will bring. This is it at the moment from the MSDUG. Sincerely, JoKi PS: Any comment is welcome, please don't hesitate... Sincerely, JoKi Update: Mauritius Software Craftsmanship Community (MSCC) Finally, back in May 2013 this whole idea got more serious and the Mauritius Software Craftsmanship Community (or short: MSCC) is alive and operational. Currently, we have our regular 'Code & Coffee' sessions on every Wednesday morning, and from time to time organised gatherings on Saturdays. More details and the ability to join the MSCC is possible via the official community site on Meetup.com. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'plans-for-mauritius-software-developer-user-group-msdug.html'; this.page.identifier = '9271ed1c97_idplans-for-mauritius-software-developer-user-group-msdug'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "plans-for-the-end-of-the-year---.html": {
    "href": "plans-for-the-end-of-the-year---.html",
    "title": "Plans for the end of the year... - Get Blogged by JoKi",
    "summary": "Plans for the end of the year... Copy Markdown View Markdown GitHub Markdown Source Plans for the end of the year... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'plans-for-the-end-of-the-year---'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'plans-for-the-end-of-the-year---'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Plans for the end of the year...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-11-28 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Hm, not yet. Well, we already know that members of my family come to stay at our place (for five and two weeks) but aside of this, we do not have any detailed plans on how to spend Christmas and New Year's Eve. But for sure, it will be my first Christmas ever at the beach. We already went shopping for tree and Christmas decorations last weekend. In terms of business, well, I do not know, if there will be an End of Year party 2008. Currently, there are no plans - or at least I have no information about that. Maybe there will be a surprise... Who knows. Kind regards, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'plans-for-the-end-of-the-year---.html'; this.page.identifier = '9271ed1c97_idplans-for-the-end-of-the-year---'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "playing-ingress-by-niantic-labs-in-mauritius.html": {
    "href": "playing-ingress-by-niantic-labs-in-mauritius.html",
    "title": "Playing Ingress by Niantic Labs in Mauritius - Get Blogged by JoKi",
    "summary": "Playing Ingress by Niantic Labs in Mauritius Copy Markdown View Markdown GitHub Markdown Source Playing Ingress by Niantic Labs in Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'playing-ingress-by-niantic-labs-in-mauritius'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'playing-ingress-by-niantic-labs-in-mauritius'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Playing Ingress by Niantic Labs in Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-04-21 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); As of writing this article it is quite tough to play Ingress here in Mauritius. Unfortunately, it seems that there is only a low awareness of this great Android app by Niantic Labs. \"The world around you is not what it seems.\" Absolutely right! Using the Ingress scanner app opens your view of your environment to a completely new world of discovery, excitement and you get drawn into an epic battle between the factions in Ingress, namely Enlightenment and Resistance. Niantic Labs is an off-spring from Google and develops Ingress independently from the Big Brother of internet searches and activities. What is it all about? Short version: World domination! I won't repeat myself here as there are already very good introduction articles and video tutorials about what Ingress is. My recommendation is to checkout the following resources: the official Google+ page of Ingress (nice vanity URL), the support information on Ingress Scanner Technology (IMHO, best resource), the Ingress Community and the official YouTube channel of Ingress to get latest information about new technology, latest features for your scanner app, and of course to watch the battle reports of other players world-wide. It's great fun and very interesting indeed! How to start? First, request an invite on the Ingress website and get yourself an activation code for your future agent career in the Ingress reality. Unfortunately, this will take some time, frankly speaking a fortnight if you're lucky. I used this method to retrieve my invites twice and it took roughly 11 days until I received mail from P.A. Chapeau. Yes, there are at least two players of Ingress in our household... and maybe two more in the future. An alternative way would be submit any kind of Ingress-related artwork to the Google+ page, and see whether one member of Niantic Labs is getting in touch with you privately. This might eventually shorten the waiting period by going the extra-mile but due to lack of experience I can't comment on this. Simply checkout the existing artwork and follow the responsible people on G+. Playing Ingress in Mauritius At the time, I started to play Ingress, there were approximately 8-9 portals in Port Louis only and collecting Exotic Matter (XM) was quite a challenge. I would assume that those portals were just the initial batch of locations Niantic pinned onto the map. Intel map of Ingress portals in Port Louis Currently, the highest density of portals is obviously in Port Louis. The easiest way is to start at the Caudan Waterfront and walk to Jardin de la Compagnie and round up your tour at the Cathedral. But thanks to the possibility to submit new portals the numbers are increasing and it is actually fun to discover and report places for the game play. Levelling up As a new agent you are starting on experience Level 1, and one of the aims you should follow is to gain more Action Points (AP) and level up quickly. Not only do get access to higher energy-loaded resonators but also acquire more powerful XMP bursters to cause quite some damage to the opposite faction. Furthermore, having the ability to link portals between each other based on the possession of their related portal keys, it is essential to maintain high level portals as their link range is higher. This allows cross-country connections and builds the base for huge control fields (CF) in order to gain Mind Units (MU). Mauritius covered by several Control Fields (CFs) Control fields are created by forming a triangle of links between three portals. But be aware that portals, having 8 resonators, can only cater for a maximum of 8 links after all. Ingress is about social gaming - work together Of course, no one can stop you from running around as a lone-some cowboy capturing and energizing portals. But unfortunately there is a slight catch. Higher levels can only be achieved by team-work. Details on a specific portal. It is vital to have multiple agents There are clear restrictions on the number of resonators a single player is allowed to place/upgrade on a portal. So, there is no chance to raise a portal to Level 8 on your own! Which is fantastic to my opinion and urges for communities and organised Ingress runs. The reports from other parts of the world have been amazing so far, and I would love to experience something similar in Mauritius, too. Luckily, I had a chance to play with agent Stumpsand - an Australian fellow on vacation, and due to his Level 5 status I quickly got new gear and was able to gain a lot of APs in a shorter time than before his presence. With his energized Level 4 portals at Grand Bassin and Rose Hill and my portal keys to almost any other portal on the island it was absolutely easy to create my first bigger CFs (see image above). Ingress is about participation - Shape the game Yes, you make an impact on the game! Simply by either submitting new portals to Niantic Labs to be reviewed and added to the augmented reality map in the future or by reviewing and improving existing portals. Both is possible through the Ingress Scanner app. As of writing this article, I think that I have submitted already more than 20 new portals of which 10+ are already on the map, and I suggested minor improvements to existing ones, like ie. the position of the MCB HQ at St. Jean which is currently not accessible from the public area. And I doubt that MCB would be happy to have masses of Ingress players on their gated property. The future of Ingress in Mauritius It looks bright! And I'm hoping that this article gives you a brief overview and attracts more Mauritian players to join Ingress - either the Enlightenment or the Resistance for the sake of good game play! Are you already an agent? What do you think of Ingress? For sure this isn't going to be the last article on Ingress in Mauritius. Stay tuned! Updates Ingress agent Stumpsand mentioned on G+ that there are quite a number of free WiFi areas near portals: \"Just a quick comment. You don't necessarily have to have a smartphone to play in Mauritius. A number of the portals I have visited have free Wifi within range so you could play with a tablet.\" Some days ago, and also notified in Google+ by agent Stumpsand, I came across a great article by Tim Bray on Advanced Ingress techniques. Also have a look at the other resources in his series: Ingress, Things About Ingress, Ingress, Month 3, Ingress Weekly, and Ingress Tourism. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'playing-ingress-by-niantic-labs-in-mauritius.html'; this.page.identifier = '9271ed1c97_idplaying-ingress-by-niantic-labs-in-mauritius'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf---tag-0.html": {
    "href": "polyphasenschlaf---tag-0.html",
    "title": "Polyphasenschlaf - Tag 0 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Tag 0 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Tag 0 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-0'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-0'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Tag 0') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-27 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ausgehend von den Erwähnung auf dem Blog von Benjamin Nitschke zum Thema PolyNapping haben die Erfahrungsberichte von Steve Pavlina den Wunsch bei mir erzeugt, wieder ein wenig mehr in Experimente am eigenen Körper einzusteigen. Diese Phase wäre nun inzwischen die dritte. Nachdem ich etwa mit 15, 16 Jahren erste Schritte in den Bereichen Autogenes Training und Selbstsuggestion unternommen habe. Sowie später dann wieder mit meditativen Übungen entsprechende Erholung und Ausgleich erfahren konnte, interessiert es mich schon inwiefern sich die Erfahrungen von Steve auf den eigenen Körper übertragen lassen. Meine aktuellen Schlaferfahrungen liegen derzeit darin, dass ich einen Drei-Std Schlafrhythmus benötige, um entsprechend erholsam den Tag zu beginnen. Also entweder drei, sechs, neun oder zwölf Stunden am Stück. Jede andere Stundenanzahl sorgt lediglich dafür, dass ich mich unausgeruht und schwer fühle. Gerade in Zeiten mit höherem Arbeitspensum im Job macht sich das extrem bemerkbar, da ich hier über Wochen hinweg ein regelmäßiges Schlafpensum von entweder drei oder max. sechs Stunden habe. Sobald ich in die \"Versuchung\" komme vier respektive fünf Stunden zu nächtigen, ist der nächste Tag nicht annähernd so produktiv als wenn ich nur drei oder dann doch sechs Stunden Erholung genossen hätte. Auf Dauer hat sich dabei zusätzlich herausgestellt, dass drei Stunden Schlaf pro Tag zu wenig sind. Was sich aber leicht durch einen weiteren Schlafblock ausgleichen lässt. Aktuell liegt meine optimale Schlafenszeit zwischen 2:00 und 8:00 Uhr morgens. Das lässt sich auf Dauer problemlos im Alltag umsetzen und produktiv nutzen. Soweit mal der aktuelle Status zu meinem Zustand. Die Selbsterfahrungsberichte von Steve haben mir wiederum eine Bestätigung meiner eigenen Thoerien gegeben... Das aktuelle Schlafverhalten des Menschen ist domestiziert und entspricht meines Erachtens nicht seinem instinktiven Verhalten. Ich kann es derzeit nicht direkt erklären, jedoch an vorhandenen Abläufen vergleichen bzw. nachvollziehbar visualisieren. Meine persönliche Erfahrungen haben dabei selbstredend einen hohen subjektiven Einfluss auf meine Theorie. Beispielsweise scheint es inzwischen einen medizinischen, genauer genetischen Grund für die Splittung in Frühaufsteher und Langschläfer zu geben - Langschläfer sind dafür in den Abendstunden aktiver, es entsteht also kein Defizit im Tagespensum, sondern lediglich ein zeitlicher Offset zur Gesellschaft. Für mich war es und ist es seit jeher eine Qual vor 8:00 Uhr aufzustehen, geschweige denn irgendetwas Produktives auf die Beine zu stellen. Ich würde auch nicht behaupten, dass ich ein Morgenmuffel bin, nein das nicht, nur eben nicht zu gebrauchen... Man nannte mich den Schläfer in Kindergarten und Schule... \uD83D\uDE09 Etwa zwei Stunden später ist das überhaupt kein Problem mehr. Und in den Nachmittags- und Abendstunden werde ich erst so richtig fit. Spannen wir den Bogen zurück zu meiner Theorie bzw. der impliziten Bestätigung durchs Steves Erfahrungsberichte. Bereits während der Schwangerschaft weisen Embryonen ein anderes Schlafverhalten auf als beispielsweise die werdenden Eltern. Ich glaube, dass eine Befragung von schwangeren Frauen einen hohen Prozentwert auf die Frage der \"nächtlichen Aktivität des Ungeborenen\" mit sich bringen dürfte. Und entsprechendes Verhalten zeigt sich ganz klar bei Säuglingen. Alle vier Stunden ist Raubtierfütterung angesagt. Das ist gängige Empfehlung von Medizinern und junge Eltern können sicherlich genug dazu sagen. Denn hier zeigt sich, dass der Säugling zunächst einmal keinerlei \"Respekt\" für das Schlafverhalten eines Erwachsenen aufbringt. Hier zählt der Instinkt, die Natur und die einfachen Bedürfnisse. Und genau dieses Schlafverhalten wird im Laufe der Monate und Jahre konditioniert auf Mittagsruhe und lange Schlafperiode während der Nacht. Und spätestens ab der Schulzeit gehen die Kids auf eine einzige Schlafphase während des Tages über. Mit dem Alter ändert sich das übrigens wieder. Hier findet dann zumindest wieder Mittagsschläfchen Einzug in den Alltag - was jedoch bei Arbeitnehmern verpönt ist. Aber zusammenfassend haben wir ein Schema der Domestizierung des Schlafverhalten eines Menschen von Polyphase über Biphase zur Monophase. Welche dann zumindest wieder zu einer Biphase zurückmutiert. Leider habe ich keine Erfahrungen zum Schlafverhalten von älteren Menschen. Aber ich meine, dass auch hier die Tendenz zur Polyphase mehr Beachtung erfährt. Die Annahmen von Säuglingen und Kleinkindern basieren zwar vorwiegend auf den Beobachtungen meiner eigenen Geschwister, was sich aber immerhin über lockere 15 Jahre hinweg zieht. Ich bin mal gespannt, wie es nun mit der nächsten Generation an Kindern in unserer Familie sein wird... Ähnlich wie Steve Pavlina möchte ich dieses Blog dafür nutzen, meine eigenen Erfahrungen bezüglich polyphasigem Schlaf zu dokumentieren und nachträglich bewerten zu können. Der Start für das Selbstexperiment ist auf den 27.03.2006, 1:00 gelegt. Analog zu Steve erscheint mir der 4 + 20 Rhythmus am geeignesten zu sein, da er sich bereits gut mit meinem aktuellen Tagesablauf deckt. Damit ergeben sich dann 1:00, 5:00, 9:00 (Zugfahrt), 13:00 (Mittagessen), 17:00 (Büroschluß) und 21:00 (Zugfahrt) als Schlüsselzeiten für den Kurzschlaf. Ich bin sehr gespannt, ob und wie sich die Umgewöhnung vollziehen wird. Insgesamt stehe ich dem Experiment sehr positiv gegenüber, da es meiner latenten Überzeugung, dass das gegenwärtige soziale Schlafverhalten unpassend ist, weiteren Nährboden liefert. Schlafzeiten: 1:00, 4:00, 6:00, 9:00, 13:00, 17:00 und 21:00 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf---tag-0.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf---tag-0'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf---tag-1.html": {
    "href": "polyphasenschlaf---tag-1.html",
    "title": "Polyphasenschlaf - Tag 1 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Tag 1 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Tag 1 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-1'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-1'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Tag 1') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-28 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Der erste Tag beginnt direkt im Alltag. Früher als sonst bin ich ohne sonstige Ermüdungsanzeichen Richtung Bahnhof gegangen, um mit dem nächsten Zug ins Büro zu fahren. Soweit alles okay - ein wenig ungewohnt, da eine gute Stunde früher als sonst komme ich also in Kaiserslautern an und richte mir wie sonst auch den Arbeitsplatz ein - Laptop anschliessen und Bootprozess anwerfen. Gutes Stichwort, denn 9:00 steht der nächste persönliche Reboot an. Da ich mir Schlafsack und leichte Decke mitgenommen habe, kein Problem. Schlafsack ausgeworfen und hingelegt. Countdown-Timer auf 30 Minuten und ab geht's ins Traumland. Naja, die geräuschliche Kulisse hat schon wesentlich mehr Umfang als in meiner Wohnung. Zudem ist hier auch mehr los... Schliesslich kommen ja die Kollegen ebenfalls um diese Uhrzeit ins Büro. Erste positive Überraschung: \"Find' ich gut.\" - O-Ton von meinem Chef. Hey, cool, da steht dem Polynapping ja hoffentlich nichts im Wege. Okay, erst mal 30 Minuten schlafen, dann geht's weiter. Es dauert schon einiges an Zeit bis ich das Gefühl bekomme, dass ich ziemlich entspannt und relaxed liege. Fühlt sich eigentlich mehr wie Autogenes Training denn Schlaf an. Nun, ich hoffe, dass das mit der Zeit besser werden dürfte. Glücklicherweise bin ich ziemlich empfänglich für Autogenes Training - es wirkt sozusagen immens bei mir. Keine Ahnung warum, aber unabhängig, ob nun Wellenprinzip und Fluß innerhalb des Körpers oder die große Schwere, ich habe bisher immer die volle Packung mitgenommen. Übrigens ohne externe Audiostimulationen, allein durch eigene Suggestion und Klärung der Gedanken. .o( Hm, klingt wahrscheinlich ziemlich abgedreht für andere... ) Okay, direkt zum Wecksignal aufgestanden und die Augen gerieben. Scheinbar bin ich doch noch kurz in den Schlaf abgesackt. Fein, es klappt also auch bei lauterem Umfeld. Und zweite Überraschung, es gibt kaum Reaktion der Kollegen - weder neugierig noch belächelnd - Danke für eure Rücksicht und Verständnis - klasse! So, kurz was trinken (Wasser pur) und eine Kleinigkeit (Apfel) essen und dann geht's ans Tageswerk. Wir haben heute noch einiges zu bewältigen, da unser Kunde mit einem aktuellen Release des Projekts versorgt sein möchte. Okidoki, die Tasten klippern nur so vor sich hin und ich komme eigentlich wie sonst auch gut mit meiner Arbeit voran - keine spürbaren Einschränkungen bisher feststellbar. Gut, umso besser - warten wir mal wie es sich im weiteren Verlauf des Tages entwickelt. Auslieferung beim Kunden ist inzwischen erfolgt und wir nähern uns der \"magischen\" 12:30 - allgemeiner Abflug zum Mittagessen. Ich verzichte dankend, da ich meine nächste Schlafsession um 13:00 halten möchte. Passt ja wunderbar, wenn so gut wie alle während der Mittagszeit außer Haus sind. Allein mit den Rechnerlüftern... wie romantisch. \uD83D\uDE09 Alles klar, kurz nach eins ist das Telefon auf Abwesend/Besetzt eingestellt und ich mache es mir wieder mit der Decke auf dem Schlafsack gemütlich. Irgendwie wurde es auch Zeit. Ich weiß nicht wie es formulieren soll, aber ich hatte eine gewisse Vorfreude mich hinlegen zu dürfen. Naja, wahrscheinlich Einbildung, wird aber weiter beobachtet werden. Und wieder Countdown auf 30 Minuten eingestellt. Erneut keine Störung außer der Geräuschkulisse, aber abermals nur Autogenes Training: ruhige, kontrollierte Atmung und Gedanken entfernen - geistige Leere produzieren. Dennoch brauche ich nach Ablauf der Zeit drei, vier Sekunden, um wieder auf die Höhe zu kommen. Ich fühl' mich sehr gut. So, mal kurz um die Ecke ein wenig Obst einkaufen und ebenfalls Mittagessen speisen. Auf dem heutigen Speiseplan stehen Bananen und Orangen dazu gibt's Tafelwasser. Entweder bilde ich mir meine geistige Fitness nur ein, oder es ist wirklich so. Im Vergleich zu den vielen Tagen davor - also nach Ausflug mit den Kollegen und Verzehr des Tagesmenüs - geht die Arbeit leichter von der Hand. Es fehlen effektiv zwei Dinge: Schweregefühl im Magen Niedergeschlagenheit im Kopf Okay, ich hab' nur wenig gegessen. Die nächsten Stunden sind auf alle Fälle wieder mit sehr viel Arbeit gefüllt. Die erste Resonanzen vom Kunden sind eingegangen, die Software zeigt Anomalien und für 17:30 wird eine Besprechung angesetzt - okay, passt. Das nächste Schlafintervall findet um 17:00 mit 30 Minuten Dauer statt - phantastisch. Der heutige Arbeitsverlauf hat wie die Faust aufs Auge zu meinem Schlafplan gepasst. So könnte es die nächsten Wochen laufen. Meine Pflichtaufgaben konnte ich bis kurz vor fünf erledigen, noch ein wenig leichte Tätigkeiten, wie Onlineforen und Mails beantworten, dann wieder eine Etage tiefer den Schlafsack drücken. Die Geräuschkulisse ist ein wenig höher, da die Kollegen in Gespräche vertieft sind, aber ich empfinde es als akzeptabel. Mein Dank gilt meinen Kollegen für deren Rücksicht auf meine exotischen Ideen. Zwischenzeitlich kam dann doch mal die ein oder andere Frage auf, warum ich denn hier herumliegen und schlafen würde. Nun, wir sprechen offen über mein Experiment zur Veränderung des Schlafrhythmus. Selbstverständlich fallen dann sofort diverse Schlüsselbegriffe wie REM-Phase während des Schlafs und gesundheitliche Bedenken. Aber wie ich schon im ersten Beitrag schrieb, bin ich davon überzeugt, dass das lange Schlafen durch die Nacht das konditionierte Ergebnis der Jahrhunderte ist. Wir werden sehen, wie es sich bei mir weiter entwickelt. Zur Besprechung fühle ich mich jedenfalls gut, kleinere Gähner noch, aber ansonsten würde ich schon sagen, dass Fitness im Hirn angesagt ist. Ich spekuliere ja bereits den gesamten Tag schon, dass der mentale wie physische Einbruch heute Abend zuhause kommen wird. Ich bin mal gespannt. Noch ein paar Restarbeiten für den heutigen Tag und die Aufarbeitung zugestellter Tasks. Ich entschliesse mich dazu, dass ich heute entgegen meiner Absicht einen Zug früher fahre und schaue, dass ich das nächste Schlafintervall (21:00) zuhause im Bett absolvieren kann. Puuh, endlich raus aus dem Zug und die letzten Meter durch die milde Märznacht nach Hause. Ist etwa ein Kilometer Distanz und bei 12° Celsius ist es auch angenehm zu gehen. Aber... die Müdigkeit kündigt sich an. Es wird nun wirklich Zeit für einen tieferen Schlaf. Um kurz nach neun starte ich ein weiteres Mal den Countdown-Timer für 30 Minuten. Ahhh, welch' ein Komfort im Vergleich zum Schlafsack auf dem Boden. Hm, sicherheitshalber stelle ich mir den normalen Wecker auf plus 5 Minuten - nur nicht verschlafen. Es stellt sich aber heraus, dass der zusätzliche Wecker nicht notwendig war. Erstaunlich erfrischt und entspannt werde ich vom Timer geweckt. Nach einem kurzen Streck- und Dehnanfall - insbesondere Katzen vollziehen dies ebenfalls bevor sie \"aufstehen\" - widme ich wieder meinem Haushalt zu. Hunger habe ich eigentlich keinen und Wasser ist noch ausreichend vorhanden. So, nun sitze ich hier um Mitternacht und schreibe weiter an diesem kurzen Bericht über den ersten Tag des Polynapping-Experiments. Um 1:00 sind die ersten 24 Stunden vorüber und ich bin wirklich sehr gespannt wie es weitergehen wird. Schlafzeiten: 1:00 5:00 9:00 13:10 17:30 21:15 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf---tag-1.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf---tag-1'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf---tag-3.html": {
    "href": "polyphasenschlaf---tag-3.html",
    "title": "Polyphasenschlaf - Tag 3 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Tag 3 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Tag 3 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-3'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-3'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Tag 3') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-30 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); 25 Minuten Die Umstellung auf 25 Minuten hat bisher keine negativen Erscheinungen im Vergleich zu den 30 Minuten zum Vorschein gebracht. Ich denke, hier wird es sich erst mit der Zeit zeigen, ob es besser oder schlechter war. Ich werde aktuell bei den 25 Minuten bleiben. Denn lieber innerhalb der REM-Phase wach werden, als danach nicht mehr oder nur sehr schwer. Gerade nachts habe ich nun den zweiten Wecker, der immer mit \"plus 5 Minuten\" zusätzlich aktiv ist, außerhalb meiner direkten Reichweite platziert. Zum Ausschalten heißt es nun entweder aufstehen oder aus dem Bett fallen. \uD83D\uDE09 Power Napping Inzwischen habe ich mir mal ein paar Artikel und Informationen zum Thema Power Napping besorgt und diese durchgearbeitet: https://de.wikipedia.org/wiki/Powernapping und https://de.wikipedia.org/wiki/Superschlaf https://www.aerztezeitung.de/docs/2004/09/17/167a1601.asp?cat=/medizin/stress https://www.newscientist.com/article.ns?id=dn2328 https://www.bfw-pp.de/muenchen/betrieb108.php https://www.3sat.de/3sat.php?https://www.3sat.de/nano/cstuecke/46618/index.html Insgesamt gibt es, zumindest so scheint es gegenwärtig, nur positive Aussagen zum gepflegten Mittagsschläfchen. \"Da das Gehirn während dieser Phase stärker als sonst durchblutet ist, scheint der REM-Schlaf wahrscheinlich vor allem der Erholung des Nervensystems und der Psyche zu dienen.\" Dem kann ich nach meiner kurzen Experimentalzeit definitiv zustimmen, da ich tagsüber wesentlich fitter als zuvor am Arbeitsplatz bin. Gerade nach der Mittagszeit. Eine weitere Ergänzung hierbei mag sicherlich auch sein, dass ich inzwischen synchron zu den Schlafphasen meine Essgewohnheiten am Umstellen bin. Schlafen, Essen und Arbeiten Da es in meinem zeitlichen Ablauf keine klassische Trennung zwischen Tag und Nacht mehr geben wird und gegenwärtig die Wachphasen alle gleichberechtigt sind, stelle ich entsprechend auch meine Ernährung auf die neuen Gegebenheiten um. Im Gegensatz zu früher - kein Frühstück, umfangreiches Mittagessen und gemischtes Abendessen - sieht die Situation so aus, dass ich jeweils direkt zu Beginn einer Wachphase Nahrung zu mir nehme. Und dabei auch darauf achte, dass es nicht zu \"schwer\" und vor allem nicht zu umfangreich ist. Also statt der üblichen zwei bis drei großen Mahlzeiten gehe ich viele (bis zu sechs) kleinere Einheiten über. Und liege damit eigentlich auch wiederum voll im Trend der Ernährungswissenschaften und Sportmedizin. Hier gilt schon längerem, dass viele, kleine Speisen dem Körper besser bekommen. Außerdem kann man auf diese Art und Weise die Variantion der Zutaten einfacher gestalten. Und nein, ich nehme keine Schnellgerichte oder Mikrowellenfutter zu mir. .o( Isch abbe gar keine Mirkowelle ) - wozu auch? Man hat ausreichend Zeit das Essen vor dem Schlafen vorzubereiten und nach dem Schlaf weiter zu machen. Schliesslich wird keiner behaupten wollen, dass die Zutaten durch eine Pause von 25 Minuten ungeniessbar werden, oder? Kleiner Nebeneffekt: Man träumt vom Schlaraffenland \uD83D\uDE09 Kurzes Intervall (wahrscheinlich) besser Nach der Sichtung einiger Onlineressourcen (u.a. Links der oben genannten Quellen) und einer Grafik über die verschiedenen Schlafphasen während der Nacht wird mein Ansatz zur Verkürzung der Pause gut untermauert. Es gilt zu verhindern, dass der Körper in die Leichtschlafphase 1 abtaucht. So lange dies erfolgreich geschieht und wir in der REM-Phase bleiben, besitzt das Gehirn ausreichend Möglichkeiten die Ereignisse der vorherigen Stunden zu verarbeiten und einzuordnen. Festplattencache reorganisieren Ich habe mich letztens gerade mit Thomas über eine Metapher meines Experiments auf die Bedeutung der REM-Phase unterhalten. Irgendwie bin im Verlauf des Gesprächs auf den Vergleich zu einer Festplatte gekommen. Nehmen wir einfach mal an, unser Großhirn repräsentiert eine Festplatte mit IO-Schnittstelle, Controller, Cache und Magnetspeicherplatten. Coole Vorstellung, oder? \uD83D\uDE09 Gut, also während der Wachphase füllt sich über die IO-Schnittstelle (Haut, Augen und Ohren) und den Controller zunächst einmal nur der Cache (Kurzzeitgedächnis) unser Festplatte. Erst durch den Beginn des Schlafes stehen dem Controller sozusagen ausreichend Ressourcen zur Verfügung, um eine geschickte und wissensreiche Langzeitspeicherung auf den magnetischen Platten zu realisieren. Spannenderweise sind die körperlichen Bewegungen während dieser Zeit nahezu gleich Null. Es erfolgt also keine Ablenkung. Unser Controller kann sich also voll und ganz auf die Filterung und Speicherung der Informationen konzentrieren. Hm, das bringt mich auf eine weitere Idee... Progressive Muskelentspannung Ebenfalls in einer der genannten Quellen bin ich über den Zusammenhang gestolpert, dass beispielsweise Yoga, Autogenes Training oder auch Progressive Muskelentspannung eine Beschleunigung zum Erreichen der REM-Phase mit sich bringen würden. Durch die gesteigerte Durchblutung und Entspannung der Muskulatur kann sich der Körper eigentlich unabhängig von den Aktivitäten des Gehirns während der Reorganisation der Datenmengen über Wasser halten. Somit werden wiederum Ressourcen für unser Hirn frei, die es effizient für Traum und Schlaf zu nutzen gilt. Körper und Geist Hm, entweder fange ich langsam an zu spinnen, oder aber es erschliessen sich mir einfach nur offensichtliche Zusammenhänge der unterschiedlichen gesundheitlichen Bereiche. Es ist wirklich spannend wie sich die einzelnen Puzzleteile des menschlichen Körper und Geistes zusammenfügen. Im fernöstlichen Sinne ist dies ja bereits seit wesentlich mehr Jahrhunderten (-tausenden) bekannt und dokumentiert. Vielleicht sollte ich doch wieder anfangen, mich mehr mit asiatischer Literatur und Sportarten zu beschäftigen. Wobei mich hier vielmehr die \"kontaktfreien\" Gebiete interessieren. Also, Tai Chi, Reiki, Yoga und so. Mal sehen, was in meiner Bibliothek zu finden ist. Hm, war der dritte Tag nun schon rum, oder doch nicht... ? Für Anmerkungen und weiterführende Informationen zum Thema schreibt mir bitte einen Kommentar. Bis denne, JoKi Schlafzeiten: 1:40 5:23 9:03 13:22 17:23 23:38 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf---tag-3.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf---tag-3'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf---tag-4.html": {
    "href": "polyphasenschlaf---tag-4.html",
    "title": "Polyphasenschlaf - Tag 4 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Tag 4 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Tag 4 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-4'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-4'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Tag 4') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-31 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); 25 Minuten Intervall definitiv besser So, inzwischen kann ich ganz klar behaupten, dass die Umstellung auf 25 Minuten für die Dauer meiner Schlafphasen definitiv eine gute Entscheidung war. Tagsüber würde ich zwar weiterhin zu 30 tendieren, aber gerade nachts erweisen sich die 25 Minuten als sehr gut. Die Ursache sehe ich darin begründet, dass ich zuhause wesentlich weniger Umweltgeräusche - lediglich der Kühlschrank brummelt als mal vor sich hin - vorfinde. Dies wirkt sich positiv auf den initialen Wechsel in die REM-Phase aus und damit wird diese ebenfalls früher verlassen und das \"Risiko\" des Überschlafens steigt. Mal sehen, vielleicht werde ich in der Mitte der nächsten Woche mal einen MixedModus probieren. Geistiger Tunneleffekt Vorhin (Heute klingt doof g) habe ich mich ein wenig mit Thomas über einen wirklich erstaunlichen \"geistig-visuellen\" Effekt unterhalten. Und zwar ist mir das ganz extrem in meiner letzten Büroschlafphase gegen 17:40 aufgefallen. Beim Dösen habe ich leichte Farbspiele und unscharfe Realbilder vor meinen geschlossenen Augen (hm, keine Ahnung wie es besser beschreiben sollte...). Mit dem Übergang in die REM-Phase scheint man visuell abzusacken. Als ob man einen Schacht nach unten fährt und nach oben schaut. Wieso ist mir das überhaupt aufgefallen? Eigentlich ist mir der Gegeneffekt viel extremer aufgefallen, da ich anscheinend gerade am Beginn der REM-Phase war und ein Kollege im Büro ein lautes Geräusch verursacht hat, welches mich \"hochfahren\" liess, wenn auch nur im geistigen Sinne. Hehehe, wie passend manche Formulierungen der deutschen Sprache doch sein können. Nach einer kurzen Abschätzung, dass keine Gefahr für den eigenen Körper besteht, ging's direkt wieder \"abwärts\" und ich bin erst ca. 12 Minuten später durch einen Telefonanruf wach geworden. Falls sich jemand gerade an die Szene in Der Herr der Ringe - Die Gefährten erinnert, als Frodo zum ersten Mal den schwarzen Reiter spürt. Also diese Kamerafahrt nach hinten mit Fokusierung nach vorne. So in etwa sieht es / fühlt es sich an. Cooles Feeling kann man da nur sagen. Interesse? Ab und zu kommen noch doch Fragen und Anmerkungen von den lieben Kollegen im Büro. Wirklich spannend, da man selbst gefordert wird, über die Sachverhalte nachzudenken bzw. ein wenig zu recherchieren. Heute hatten wir ein bis zwei Mal das Thema PowerNapping auf dem Tablett. Denn im Gegensatz zu den anderen verzeichne ich am Nachmittag keine Reduzierung der Aufmerksamkeit. Bitte nicht missverstehen, ich möchte niemand in Misskredit bringen, sondern stelle lediglich fest, dass ich mich auf mentaler Ebene aktionsreicher fühle. Und, hey, mal ehrlich gegen einen gepflegten Büroschlaf nach dem Mittagsessen ist doch beileibe nichts einzuwenden, oder? \uD83D\uDE09 Feuertaufe So, nach dem arbeitsreichen \"Tag\" im Büro stand heute \"Abend\" weiteres Programm an. Da ich ehrenamtlich in den Reihen einer freiwilligen Feuerwehr stehe, hatten wir heute Abend unsere Übungseinheit. Zwei Stunden in der Zeit von 20:00 bis 22:00 Uhr - super, trifft genau meine letzte Schlafphase... Ich bin mal gespannt, wie fit a) um 21:00+ Uhr sein werde und b) die verschobene Ruhezeit überstehe. Nun, inzwischen ist es gerade 1:31 und ich schreibe hier fit und munter an diesem Blogeintrag. Ich musste mir zwar den zweiten Wecker außerhalb meiner Reichweite positionieren, aber nach ca. 31 Minuten wurde ich wach und fühlte wie sonst auch - gut erholt und frisch. Nur schnell raus aus dem Bett bevor ich wieder einnicke. Ich bin dann sogleich unter die Dusche gehüpft und wie gesagt, tippe nun locker flockig den Beitrag. Zur Kompensation werde ich die nächsten Ruhezeiten (regulär 1:00 und 5:00) auf etwa 2:45 bzw. 6:00 oder so legen, um dann einigermaßend fliessend wieder in den gewohnten Ablauf von vier Stunden zu kommen. Rein intuitiv würde ich eine Verkürzung auf 20 Minuten vorsehen... hm, auf alle Fälle steht der zweite Wecker wieder bereit. \uD83D\uDE09 Somit haben wir die Revanche auf den Rückschlag von Mittwoch morgen... Für Anmerkungen und weiterführende Informationen zum Thema schreibt mir bitte einen Kommentar. Bis denne, JoKi Schlafzeiten: if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf---tag-4.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf---tag-4'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf---tag-5.html": {
    "href": "polyphasenschlaf---tag-5.html",
    "title": "Polyphasenschlaf - Tag 5 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Tag 5 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Tag 5 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-5'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-5'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Tag 5') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-01 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Weniger Schlaf Heute ergab sich kaum die Möglichkeit während der Arbeit einzuschlafen. Die Geräuschkulisse war leider ein wenig zu hoch. Ich möchte mich mit der Aussage nicht beschweren, sondern lediglich feststellen, dass ich nicht wirklich schlafen konnte. Stattdessen begnügte ich mich mit ein paar Minuten geschlossener Augen und autogenem Training, welches eigentlich bisher zum Schlaf führte, nicht so heute. Naja, ab nächster Woche werde ich mich ins Besprechungszimmer - sofern ungenutzt - verziehen und dort die Schlafeinheiten während des Tages umsetzen. Soziales Umfeld Ansonsten gibt es kaum Neues zu berichten. Gegen Abend bei meiner Familie eingelaufen und Thomas noch ein wenig bei der Wohnungseinrichtung geholfen. Meine abendliche Ruhezeit konnte ich auch problemlos einhalten; wiederum keine richtige REM-Phase. Naja, später vielleicht. Frisch gestärkt geht's wie abgesprochen noch auf die Piste... Kultur muss eben auch sein. Thomas meinte bereits, dass er bis maximal 0:30 Uhr dort bleiben möchte. Super, dann kann er mich auf dem Rückweg zuhause rauswerfen und werde mich der nächsten Schlafpause hingeben. Übrigens seine Premiere im Kult. \uD83D\uDE09 Ich hoffe, dass sich meine Erzählungen und deine Vorstellungen vom Kult einigermaßen erfüllt haben. Und ich hoffe, dass du dich wenigstens ein bisschen ärgerst, dass du nicht schon wesentlich früher mitgegangen bist... Somit zeigt sich auch, dass ein normales Leben mit den multiplen Schlafphasen geführt werden kann. Mal sehen wie das später wird... Konzert in der Kammgarn - Yeah, Party! Nachtplanung? Tjoa, aktuell schreibe ich zwar hier diesen Eintrag, aber das wird auch keine Stunden dauern. Mal sehen, wie ich mich gleich noch beschäftigen werde. Evtl. mal schauen, was im Buch über Tai Chi zu lesen ist. Auf alle Fälle muss ich mich körperlich betätigen, da es mich ein wenig fröstelt. Könnte sich eventuell auf zu wenig Bewegung zurückführen lassen, oder leichte Müdigkeitserscheinungen. brrr - gleich mal ein paar dicke Socken anziehen und was Warmes trinken. Bedingt traumlos Die letzten Schafpausen waren annahmslos traumlos. Zwar werden zu Beginn noch etliche unscharfe Bilder und \"Videosequenzen\" vor dem inneren Auge abgespult, aber ich würde hier sicherlich nicht von Träumen sprechen. Nach dem Wachwerden kann ich ebenfalls an nichts bis zu Beginn der Schlafphase erinnern. Aktuell befinde ich mich damit im Binärzustand - wach (1) und schlafend (0). Ich bin mal gespannt, ob und wie sich das in den nächsten Tagen oder Wochen entwickeln wird. Zwischenzeitlich werde ich meine Anstrengungen in Autogenem Training intensivieren. In der Hoffnung, dass ich zügiger den Körper zur Entspannung bringen kann, um dann meine \"Aufmerksamkeit\" ganz dem Schlaf zu widmen. Für Anmerkungen und weiterführende Informationen zum Thema schreibt mir bitte einen Kommentar. Bis denne, JoKi Schlafzeiten: 13:23 17:40 21:35 1:20 4:03 6:20 9:04 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf---tag-5.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf---tag-5'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf---tag-6.html": {
    "href": "polyphasenschlaf---tag-6.html",
    "title": "Polyphasenschlaf - Tag 6 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Tag 6 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Tag 6 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-6'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---tag-6'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Tag 6') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-02 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es wird besser Ich glaube, so langsam stellt sich mein Körper auf das geänderte Schlafverhalten ein. Die Ruhezeit habe ich nun konstant bei 25 Minuten gelassen und das klappt inzwischen. Auch wenn ich noch mit zwei Weckern arbeite, um dem Risiko des Überschlafens - also Verlassen der REM-Phase und Abtauchen in die Leichtschlafphase 1 - vorzubeugen. Alltägliche Aufgaben Nur weil ich anders schlafe, heißt ja nicht, dass die sonstigen Aufgaben nicht erledigt werden könnten. \uD83D\uDE09 Heute habe ich die Zeit genutzt, um diverse ausstehende Alltagsaufgaben umzusetzen. Nichts Spektakuläres aber immerhin... Bad reinigen, Einkaufen, Backen, etc. Keine Sorge funktioniert alles wie gewohnt. Nee, eigentlich nicht ganz, denn ich gehe bewusster und mit weniger Ablehnung an die Tasks. Ich Gegensatz zu vorher, habe ich so etwas immer als notwendiges Übel betrachtet, aber heute hat es einfach nur Spass gemacht und boot sich als willkommene Aktivität an. Wochenende... Nach den abendlichen / nächtlichen Aktivitäten am FR im Kult, reihte sich heute sogleich auch dann das nächste Event ein: The BossHoss Konzert in der Kammgarn (Beginn 20:00, Ende ca. 22:xx) Tjoa, soviel zur Planung für das 21:00 Uhr Schläfchen. Aktuell ist die Situation, dass es ausgefallen ist. \uD83D\uDE09 Leute,ich leg' mich doch nicht mitten im Konzert für 20 Minuten aufs Ohr... Nee nee, beim besten Willen nicht. Okay, dann eben Plan B und leichte Umstellung der weiteren Schlafphasen. Aktuell sitze ich gerade an der täglichen Mailflut und versuche dieser Herr zu werden. Es ist übrigens gerade 2:11 in der Frühe und ich bin guter Dinge. Leichte Schläfrigkeit ist vorhanden. Gut bei ca. 8 Stunden Aktivität und einer verpassten Ruhezeit, kaum verwunderlich. Mal sehen, wahrscheinlich werde ich mich wieder 3:00 Uhr hinlegen und versuchen, dass Defizit auszugleichen. Versuch gescheitert... Es hat nicht funktioniert wie erwartet... Trotz zwei Wecker bin ich nicht wach geworden. Inzwischen glaube ich, dass die 48 Stunden eine Art magische Grenze darstellen. Hier bleibe ich irgendwie dran hängen. Hm, nur leider habe ich momentan noch keine Idee, wie ich das überbrücken könnte. Mal sehen, wie es im weiteren Verlauf des Experiments funktioniert. Nur momentan trete ich auf der Stelle. Für Anmerkungen und weiterführende Informationen zum Thema schreibt mir bitte einen Kommentar. Bis denne, JoKi Schlafzeiten: 13:02 17:35 --21:xx-- 1:08 3:04 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf---tag-6.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf---tag-6'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf---woche-1.html": {
    "href": "polyphasenschlaf---woche-1.html",
    "title": "Polyphasenschlaf - Woche 1 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Woche 1 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Woche 1 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---woche-1'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf---woche-1'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Woche 1') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-03 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Eine Woche seit Beginn des Experiments \"polyphasiger Schlaf\" ist inzwischen vergangen und in diesem Beitrag schreibe ich mal meine Erfahrungen, Vermutungen und sonstige Erkenntnisse zusammen. Sicherlich lässt sich hier sagen, dass es sich nicht um der Weisheit letzter Schluß handelt. Dennoch fasse ich einfach mal ein paar Dinge zusammen. Dabei geht's über verschiedene Teilbereiche meines Körpers, meines GeÃ­stes und meiner Umwelt. Allgemeiner Eindruck Bringen wir es gleich auf den Punkt: Es hat bis jetzt wirklich Spass gemacht und zu keinem Zeitpunkt des Experiments fühlte ich mich weder überfordert oder nicht im Besitz meiner geistigen Kräfte. Ich konnte problemlos meiner täglichen Arbeit folgen und möchte sogar behaupten, dass ich dies produktiver gestalten konnte als zuvor. Die problematischeren Zeiten waren vor allem zwischen 1:00 und 5:00; und hier erfolgten auch die sogenannten Rückschläge. Ansonsten hat sich gezeigt, dass es sehr hilfreich war, sich ständig zu beschäftigen. Es galt sozusagen die Spannung während der Wachphase aufrecht zu halten. Meine allgemeine Erkenntnis aus dieser Woche lautet, dass es wesentlich besser ist, immer wieder kurze Schlafphasen einzulegen, als zu kurze Nächte zu haben. Dies erschliesst sich aus dem Vergleich mit den Erfahrungen aus einigen Wochen der letzten Monaten, in denen ich teilweise nur drei bis vier Stunden am Stück geschlafen habe. Diese Woche mit den multiplen Phasen brachte mir wesentlich mehr. Gesundheit Stabil. Natürlich ergeben sich aus der Umstellung einige temporäre Probleme und körperlichen Widerstände. Dennoch kann ich ganz klar feststellen, dass ich nach dieser Woche keinerlei Schäden erlitten habe. Mein Körper zeigte vorübergehend zwar diverse Symptome - eiskalte Hände, kribbelnde Fingerspitzen und heiße Ohren - dennoch haben sich keine handfesten Einbußen auf die Gesundheit ergeben. Zusätzlich zur Veränderung des Schlafens habe ich ein paar Veränderungen in Bezug auf Ernährung und Nahrungsmittel durchgeführt. So esse ich inzwischen weniger fleischhaltige Nahrung und achte vermehrt auf natürliche Nahrungsmittel mit Ballaststoffen. Zusätzlich habe ich in dieser Woche den Konsum von Koffein (durch Tee und Cola) auf Null reduziert. Die Mahlzeiten versuche ich immer direkt dem Wachwerden einzunehmen; nachts verzichte ich vollständig darauf. Ich werde weiterhin auf körperliche und gesundheitliche Signale meines Körpers achten. Nach dieser Woche kann ich mit gutem Gewissen behaupten, dass ich bewusster auf meine Ernährung, meinen Körper und geistigen Zustand geachtet habe, als in der Zeit davor. Ich habe mir sozusagen mehr Zeit für mich genommen und ich genieße diesen Umstand wahrlich. 48 Stundenbarriere Aber wie es mir aktuell erscheint, gibt es bei meinem Körper offensichtlich eine Art von Hindernis. Ich weiß noch nicht, ob es eine physische oder psychische Barriere ist. Dennoch konnte ich während dieser Woche beobachten, besser: leidvoll feststellen g, dass mein Körper mehr Schlaf einforderte als ich ihm zugestanden habe. Nun, sagen wir mal so. Mir ist klar, dass sich das Schlafverhalten von knapp drei Jahrzehnten nicht in drei Tagen umstellen lässt. Dennoch bleibt die Frage, wieso wiederholend bei ca. 48 Stunden? Und das bei unterschiedlichen Voraussetzungen? Ich habe ehrlich gesagt, absolut keine Idee geschweige denn Vorstellung. Bei den ersten beiden Einbrüche bin ich geistig locker und körperlich fit zu Bett gegangen und erst Stunden später erwacht - interessanterweise in beiden Fällen vermutlich während einer REM-Phase g - einmal nach sechs Stunden und das zweite Mal nach etwa drei Stunden. Soweit mal meine Beobachtungen zu dieser Situation. Da ich das Experiment garantiert weiterführen werde, überlege ich mir nun ein paar Strategien zur Vermeidung des \"Überschlafen\". Sicherlich kann man sagen, dass der Körper irgendwelche Defizite einfordert, aber die kann er auch genauso gut während der Schlafzeiten tagsüber einfordern. Und hier gilt es nachzubessern, denn in den meisten Fällen konnte ich am Tag nicht wirklich wegnicken, sondern lediglich mit geschlossenen Augen ruhen und ein wenig Autogenes Training umsetzen. Ich denke, dass hier noch Potenzial steckt, um eine Optimierung hervorzubringen. Vor- und Nachteile Zu den Vorteilen meines Schlafexperiments zähle ich definitiv die Tatsache, dass ich mir weniger Druck zur Erledigung von Aufgaben während des Tages mache. Da ich im Bewusstsein unterwegs bin, dass ich einige Aufgaben auch während der Nacht erledigen kann, geniesse ich sozusagen diese neue Art von Freiheit. Bei Betrachtung der durchgeführten Tätigkeiten kann ich auch nur Vorteile entdecken. Ich konnte Dinge erledigen, die mir schon seit längerem ein Dorn im Auge waren und ich mangels Motivation, Fitness und geeignetem Zeitpunkt immer wieder hinausgeschoben hatte. Was will man auch nachts schon machen, außer versuchen nicht einzuschlafen? g Hier gilt es sich zu beschäftigen und da sind liegengebliebene Aufgaben (im Haushalt) ein wahrer Genuß. Zumindest freut es mich zu sehen, was ich zusätzlich umsetzen konnte. Die Nachteile habe ich weitestgehend schon in den letzten Berichten geschildert. Dennoch sehe ich diese Nachteile gegenwärtig nur in der Umstellungsphase als negativ an. Meine Vermutung - oder Schönwetterdenken - geht dahin, dass mit dem erfolgreichen Umstellen, die Nachteile geringer werden und vielleicht sogar komplett verschwinden. Okay, eins wird sicherlich bleiben: Tagsüber geeignete Ruheplätze zum Dösen zu finden. Wobei dies eher ein soziales Problem repräsentiert. Soziales Umfeld Die bisherigen Reaktionen und Meinungen zu meinem Experiment skalieren von \"Tolle Idee, sag' Bescheid wie's voran geht\" bis \"Alles krank und wider der Natur\". Nun, ehrlich gesagt ist es mir in gewisser absolut egal, wie die Reaktionen einzelner Menschen sind. Denn es ist meine persönliche Sache, mein Experiment und meine Freiheit. Da ich weder andere in ihrer Freiheit beschneide noch dazu ermuntere, das Experiment an sich selbst durchzuführen. Dennoch finde ich es spannend, welche Diskussionen sich über etwas so simples und alltägliches wie Schlaf ergeben können. Wissenschaftlich betrachtet steht die Schlafforschung noch am Anfang der Erkenntnisse und die Meinungen von Experten divergieren in alle möglichen Richtungen. Was ich auf alle Fälle als sehr positiv hervorheben möchte, ist die Tatsache, dass auf der Arbeit meine Umstellung vollständig toleriert und teilweise sogar gutgeheißen wird. Umgekehrt kann ich auch nachweislich behaupten, dass meine Leistungsfähigkeit nicht gelitten hat, sondern im Gegenteil sogar effizienter wurde. Oder anders formuliert, ich habe immer noch wie jede Woche zuvor Überstunden ohne Ende... \uD83D\uDE09 Privat lassen sich die geänderten Schlafzeiten so ziemlich mit allem vereinbaren, was ich auch schon zuvor an AKtivitäten hatte. Einzig der zeitliche Rahmen ist konkreter abzustecken und insbesondere der lange Abend am Samstag könnte zum Scheitern während der Nacht geführt haben. Aber insgesamt ergeben sich kaum Probleme, die es anders nicht auch geben würde. Weitere Schritte Ich experimentiere weiter und werde berichten... \uD83D\uDE09 Welche Varianten in den nächsten Tagen und Wochen probiert werden, kann ich aktuell noch nicht abschätzen. Ein Versuch wird darauf hinaus laufen, dass ich die Schlafphasen nachts auf 20 Minuten verkürzen werde, einfach um das Risiko des Überschlafens - also den Wechsel in die Leichtschlafphase 1 - zu reduzieren. Im Gegenzug werde ich zusehen, dass ich tagsüber mehr REM-Schlaf umsetzen kann. Hier gibt es sicherlich noch einiges an Möglichkeiten. Vielleicht werde ich probeweise eine Fastenzeit einlegen, mal sehen... \"Haftungsausschluß\" Falls jemand auf die Idee kommen sollte, das Experiment selbst an sich durchzuführen, dann geschieht dies auf eigene Verantwortung und die Risiken hat jede/r für sich selbst zu tragen. Mein Schlafexperiment betrifft ausschliesslich mich persönlich und ich nutze diese Plattform ausschliesslich dafür, meine Erfahrungen und Erkenntnisse schriftlich niederzulegen. Für Anmerkungen und weiterführende Informationen zum Thema schreibt mir bitte einen Kommentar. Bis denne, JoKi Schlafzeiten: 1:50 5:38 10:00 13:15 17:40 21:10 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf---woche-1.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf---woche-1'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "polyphasenschlaf-tag-2-12.html": {
    "href": "polyphasenschlaf-tag-2-12.html",
    "title": "Polyphasenschlaf - Tag 2 1/2 - Get Blogged by JoKi",
    "summary": "Polyphasenschlaf - Tag 2 1/2 Copy Markdown View Markdown GitHub Markdown Source Polyphasenschlaf - Tag 2 1/2 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf-tag-2-12'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'polyphasenschlaf-tag-2-12'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Polyphasenschlaf - Tag 2 1/2') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-29 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Der erste Rückschlag ist zu verzeichnen. Dazu später mehr. Nachdem ich mich mit dem Gedanken angefreundet habe, endlich mehr Zeit zur Erledigung meiner Aufgaben zu haben, habe ich den Tag wesentlich lockerer betrachtet und bin diesen auch, wie soll ich sagen, mit weniger Hektik angegangen. Somit dann morgens im Bad noch ein wenig Zeit verbracht - Rasieren, was ich bisher meistens nur am Wochenende mit Muße erledigen konnte, danach einen kleinen Happen der frisch gekochten Grütze und dann ab an den Bahnhof, so dass ich den Zug kurz vor 8:00 noch erwische. Allein diese Tatsache, dass ich so früh ins Büro fahre, ist außergewöhnlich! Die Zugfahrt wird unangenehmer als erwartet, geradezu zur echten Tortur. Ab etwa der Hälfte der Strecke werden die Augenlider schwerer und muss etliche Sätze in dem Buch, dass ich während der Fahrt lesen wollte, mehrfach durchgehen. Ab und zu sackt auch der Kopf nach unten. Nix da, durchhalten; Schlafen ist für 9:00 angesagt. Und es bessert sich auch nach dem ich in Kaiserslautern den Zug verlassen habe und wieder aktiv bin. Okay, die Beobachtung hier ist: Aktiv bleiben, gerade am Ende einer Wachphase. Nach einem kurzen Schläfchen ist der Morgen ab 9:30 wieder voll in Ordnung und die Müdigkeit ist vollständig weg. Normales Arbeiten ist nun angesagt. Es geht gut voran, die Aufgaben gehen gut von der Hand und ich komme beim Codereview gut mit dem Refactoring voran. Meine Stimmung ist sogar ein wenig besser als sonst; weniger mürrisch. \uD83D\uDE09 Die Kollegen haben inzwischen das Büro zum Mittagessen verlassen und ich werf' mich gegen 13:10 für den nächsten Schlaf auf meinen Schlafsack. Alles in bester Stimmung, der Nachmittag verläuft genauso wie der Vormittag, keinerlei Einbußen oder \"Downzeiten\" zu verbuchen. Lediglich habe ich eisige Hände und ein leichtes Kribbeln in den Fingerspitzen. Gegen 16:45 registriere ich ein weiteres Signal meines Körpers - meine Ohren werden wärmer. Die Hände sind inzwischen wieder okay, aber die Ohren... herrje, die werden langsam richtig heiß. Ich glaube der beste Vergleich zur Beschreibung ist, wenn man im eiskalten Winter längere Zeiten draußen ist und dann in die aufgeheitze Stube zurückkommt. Dann bekommt man auch nach kurzer Zeit warme Wangen und heiße Ohren. Genau so fühlt es sich an. Leider sind noch ein, zwei Aufgaben für den Kunden zu erledigen. Die Ohren werden weiterhin wärmer und um 17:30 stoppe ich meine Aktivitäten. Ich muss mich schlafen legen, sonst wird das nichts. Nach dem Reboot wird es wieder besser. Ich fühle mich im Vergleich vor dem Schlaf wieder entspannt und ausgeruht - sozusagen aufgeladen. Die Ohren sind zwar immer noch warm, aber sie beginnen wieder abzukühlen. Komischer Effekt, oder? Die restlichen Aufgaben gehen wieder supereasy von der Hand. Also, nächstes Mal rechtzeitig schlafen legen und nicht zu sehr quälen. Ich werde auf meine Ohren achten... \uD83D\uDE09 Die weiteren Stunden verlaufen de facto wie sonst auch zu Beginn meiner Wachphasen - null Probleme und viel Energie. Auf alle Fälle eine erhebliche Verbesserung gegenüber dem Arbeitsverhalten der vorherigen Wochen. Genau wie gestern, verabschiede ich mich kurz nach 20:00 Uhr und fahre nach Hause. Auf dieser Fahrt bin ich schlauer: Telefonieren mit den Freunden. Hier noch einen Termin für's Wochenende geklärt, dort nach dem allgemeinen Befinden gefragt, und an dritter Stelle über das Experiment gesprochen. Gut, keine Müdigkeit zu verzeichnen und um Viertel nach neun abends verfrachte ich mich wieder ins Bett. Nach einem leichten Abendessen (Orangen, rote Grütze mit Sahne und viel Mineralwasser) sitze ich wieder am PC und erledige einige meine privaten Mails, checke die Beiträge in Foren und chatte mit den Freunden über Gott und die Welt. Achja, Musik darf selbstverständlich nicht fehlen. Eigentlich bin ich viel zu fit, um mich schlafen zu legen. Aber die Wachphase ist zu Ende und um 1:10 lege ich mich wieder hin. Countdown-Timer auf 30 Minuten und zweiter Wecker auf weitere 5 Minuten später eingestellt. Der Rückschlag Hm, wieso ist hell draußen? Wieviel Uhr ist es eigentlich und wieso fühle ich mich so schlapp? Es ist hell draußen, weil es inzwischen kurz nach 9:00 Uhr morgens ist. Mist, das sind dann gute 8 Stunden Schlaf. Damit habe ich auch die Vermutung für meine Schlaffheit: 8 Stunden sind nicht nur 3 teilbar. Was ist passiert, was ist fehl gelaufen? Nach Prüfung der beiden Wecker ergibt sich folgendes: Ich habe beide ausgeschaltet. Okay, keine Erinnerung daran. Dieses Phänomen von Aktivität ohne Erinnerung kenne ich aus der Vergangenheit, wenn auch nur sporadisch. Die erste Vermutung, die sich mir offensichtlich darstellt ist, zu lange geschlafen und damit die REM-Phase überschritten. Das ist mein Betrachtungswinkel, da ich am Erfolg des Experiments orientiert bin. Die andere Betrachtung ist: Was wundert mich das? Der Körper nimmt sich schon was er braucht, um vorhandene Defizite auszugleichen. Pattsituation. Persönlich tendiere ich dennoch zum Überschlafen, da ich in den letzten 48+ Stunden keinen physischen Mangel feststellen konnte. Auch keine Erscheinungen von Übermüdung oder Schlaflosigkeit. Das kenne ich zu Genüge aus der Vergangenheit. Und... ich habe mich ja absichtlich hingelegt, ohne wirklich müde zu sein. Okay, sämtliche Annahmen sind gegenwärtig nur spekulativ. Für das Schlafexperiment werde ich aus diesem Verlauf nun eine Änderung vornehmen: Verkürzung der Schlafphase auf 25 Minuten. Besonders nachts, da ich hier schneller als tagsüber einschlafe und somit eher in die REM-Phase übergehe. Gut, inzwischen bin ich wieder im Büro, erledige meine normalen Aufgaben und werde mich um 13:00 wieder hinlegen und schlafen. Für Anmerkungen und weiterführende Informationen zum Thema schreibt mir bitte einen Kommentar. Bis denne, JoKi Schlafzeiten: 1:00 --5:00-- 9:00 13:03 17:47 21:23 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'polyphasenschlaf-tag-2-12.html'; this.page.identifier = '9271ed1c97_idpolyphasenschlaf-tag-2-12'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "portless-with-firebase-emulators.html": {
    "href": "portless-with-firebase-emulators.html",
    "title": "Using portless with Firebase Hosting - Get Blogged by JoKi",
    "summary": "Using portless with Firebase Hosting Copy Markdown View Markdown GitHub Markdown Source Using portless with Firebase Hosting Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'portless-with-firebase-emulators'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'portless-with-firebase-emulators'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Using portless with Firebase Hosting') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2026-08-21 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); When developing multiple web applications and microservices locally, managing local port numbers quickly becomes tedious. Switching between localhost:3000, localhost:5000, localhost:8080, and localhost:9099 leads to port collisions, CORS friction, and cookie domain mismatches. To solve this, portless - hosted on GitHub - provides local domains without port exposure; but with automatic SSL certificates (e.g., https://myproject.localhost/) mapping seamlessly to your local dev processes. However, the main development stack targeted seems to be anything Node.js based and pairing portless with the Firebase Emulator Suite introduces unique challenges because of how Firebase orchestrates its networking and emulator processes. The Problem Standard frontend dev servers (like Vite, Next.js, or Webpack Dev Server) usually take a dynamically injected PORT environment variable or command-line argument. With Firebase Local Emulators: Multiple Co-existing Ports: Firebase spins up several emulators simultaneously (Hosting on 5000, Functions on 5001, Firestore on 8080, Auth on 9099, and the Emulator UI on 4000). Fixed Port Defaults: The Firebase CLI does not automatically adapt its Hosting emulator port to arbitrary environment variables like PORT=34567 provided by automatic reverse proxies. HTTP vs HTTPS: Firebase Hosting emulator serves over plain HTTP (http://127.0.0.1:5000), but modern browser features, secure cookies, and third-party auth callbacks frequently require HTTPS. Multiple projects: Running multiple projects requires different unique ports assigned, and humans get confused easily. Having a project-related domain as URL is a productivity booster. Architectural Obstacles & Exploration Obstacle 1: The Injected PORT Fallacy Many proxy wrappers rely on launching a child command and expecting the framework to read process.env.PORT: # What portless typically does under the hood PORT=49152 npm run dev When running firebase emulators:start, Firebase ignores process.env.PORT and defaults to port 5000 (or the port explicitly specified under \"emulators\".\"hosting\".\"port\" in firebase.json). Obstacle 2: The Root vs. User Space Split & routes.json Desynchronization portless relies on a two-tier architecture: The Reverse Proxy Daemon: Needs to bind to privileged ports (80 for HTTP and 443 for HTTPS), requiring root privileges (sudo portless proxy or a system daemon). The Developer CLI: Runs in regular user space to launch child processes and register routes on demand (portless). Under the hood, route registrations are stored in a state file called routes.json. Because of the separation between root and user space, two major issues arise: 1. The Disconnected routes.json Path When the proxy daemon runs as root, its $HOME is /root/, so it listens for route changes inside: /root/.portless/routes.json Meanwhile, when the developer runs portless as an unprivileged user, the CLI writes new mappings to: $HOME/.portless/routes.json The CLI reports success: ✔ Registered route: iosltd.localhost -> 127.0.0.1:5000 in $HOME/.portless/routes.json ✔ Starting child process: firebase emulators:start --only hosting However, navigating to https://iosltd.localhost/ in the browser fails with a 502 Bad Gateway or 404 Not Found, and the root proxy daemon emits: [portless:proxy] [WARN] Incoming request for \"https://iosltd.localhost/\" [portless:proxy] [ERROR] Hostname \"iosltd.localhost\" not found in active routes table (/root/.portless/routes.json) [portless:proxy] [INFO] Loaded routes: 0 registered targets 2. File Ownership & EACCES Permission Errors If the daemon is started pointing directly to the user's directory without dropping privileges, it creates or overwrites routes.json owned by root:root. The next time you run portless without sudo, the command immediately crashes with: node:fs:585 handleErrorFromBinding(ctx); ^ Error: EACCES: permission denied, open '$HOME/.portless/routes.json' at Object.openSync (node:fs:585:18) at Object.writeFileSync (node:fs:2334:35) at registerRoute (/usr/local/lib/node_modules/portless/lib/routes.js:42:8) at async start (/usr/local/lib/node_modules/portless/bin/cli.js:88:5) { errno: -13, syscall: 'open', code: 'EACCES', path: '$HOME/.portless/routes.json' } The Resolution Explicitly synchronize the state directory across both root and user environments using PORTLESS_STATE_DIR, and ensure the directory has user-level write permissions: # 1. Export in user environment (~/.bashrc or ~/.zshrc): export PORTLESS_STATE_DIR=\"$HOME/.portless\" # 2. When starting the elevated proxy daemon, pass the user state directory: sudo PORTLESS_STATE_DIR=\"$HOME/.portless\" portless proxy # 3. Ensure proper file ownership if root previously touched the file: sudo chown -R $USER:$USER \"$HOME/.portless\" Obstacle 3: Startup Delays with Full Emulator Suite Running firebase emulators:start without filtering launches Java-based emulators (Firestore, Pub/Sub, Storage) which takes several seconds. When portless probes the port before the Hosting emulator binds, it might report a connection timeout. Resolution: Narrow the emulator scope for web hosting development: firebase emulators:start --only hosting The Solution: Explicit Configuration The cleanest approach is to define an explicit \"portless\" declaration in your project's package.json mapping the domain alias directly to Firebase Hosting's port (e.g. 5000 or custom port like 5002), while delegating the startup to npm run dev. That's the default launch script in portless. But what if you don't have a package.json file? It won't hurt or damage your project (e.g. a static website) to create a lightweight one—it provides a standard place for dev scripts and local tooling. 1. package.json Configuration Here's a sample configuration: { \"name\": \"ios-website\", \"version\": \"2.0.1\", \"description\": \"IOS Indian Ocean Software Ltd. - Software Solutions from Mauritius\", \"scripts\": { \"dev\": \"firebase emulators:start --only hosting\" }, \"portless\": { \"name\": \"iosltd\", \"appPort\": 5000 } } Add the standard project metadata like name, version, and description. Add the portless configuration block: portless.name: Assigns the local hostname https://iosltd.localhost/. If omitted, the top-level name value is used. portless.appPort: Points the reverse proxy directly to the internal port of your Firebase emulator (5000, or whatever port is set in firebase.json). scripts.dev: Runs the Firebase Hosting emulator exclusively. This is the command portless runs automatically. 2. firebase.json Configuration Here is the corresponding firebase.json configuration, configuring static hosting and emulator ports: { \"hosting\": { \"public\": \"public\", \"cleanUrls\": true, \"trailingSlash\": false, \"ignore\": [ \"firebase.json\", \"!**/.well-known/**\", \"**/.*\", \"**/node_modules/**\" ] }, \"emulators\": { \"hosting\": { \"port\": 5000 }, \"ui\": { \"enabled\": true }, \"singleProjectMode\": true } } Tip Always ensure that \"portless\".\"appPort\" in package.json matches \"emulators\".\"hosting\".\"port\" in firebase.json. Running the Setup Starting the Dev Environment via Portless Run the portless command from the root of your project: portless This minimal approach relies on the default launch behaviour of portless and uses the configuration in package.json as described above. portless will launch the proxy route and run npm run dev in the child process. Terminal Output portless -- Proxy is running -- getblogged.localhost (auto-resolves to 127.0.0.1) -- Name \"iosltd\" (from portless.json) -- Using port 5000 (fixed) -> https://iosltd.localhost Running: PORT=5000 HOST=127.0.0.1 PORTLESS_URL=https://iosltd.localhost NODE_EXTRA_CA_CERTS=\"$HOME/.portless/ca.pem\" npm run dev > iosltd@2.0.1 dev > firebase emulators:start --only hosting i emulators: Starting emulators: hosting ⚠ hub: emulator hub unable to start on port 4400, starting on 4401 instead. ⚠ logging: Logging Emulator unable to start on port 4500, starting on 4501 instead. i hosting[iosltd]: Serving hosting files from: posts/_site ✔ hosting[iosltd]: Local server: http://127.0.0.1:5000 ⚠ emulators: The Emulator UI is not starting because none of the running emulators have a UI component. ┌─────────────────────────────────────────────────────────────┐ │ ✔ All emulators ready! It is now safe to connect your app. │ └─────────────────────────────────────────────────────────────┘ ┌──────────┬────────────────┐ │ Emulator │ Host:Port │ ├──────────┼────────────────┤ │ Hosting │ 127.0.0.1:5000 │ └──────────┴────────────────┘ ✔ Proxy active at: https://iosltd.localhost/ Verification & Testing You can now curl or open your browser directly to the clean .localhost domain with TLS: curl -k https://iosltd.localhost/ Output <!DOCTYPE html> <html lang=\"en\"> <head> <meta charset=\"UTF-8\"> <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"> <title>IOS Indian Ocean Software Ltd.</title> ... Stopping and Restarting the Proxy (Applying Changes) When you modify routes.json, change environment variables (PORTLESS_STATE_DIR), update certificates, or alter your project's port mapping, you need to cleanly stop the running portless reverse proxy daemon so the changes take effect. 1. Stopping the Proxy Daemon If you started the proxy via sudo portless proxy or as a background service: # Graceful stop via CLI sudo portless stop # Or terminate the proxy process directly sudo pkill -f \"portless proxy\" 2. Ensuring Ports 80 & 443 are Released If the proxy crashed or did not release ports cleanly, verify that no orphaned processes are occupying the privileged web ports: sudo lsof -i :80 -i :443 To forcefully release the ports if needed: sudo fuser -k 80/tcp 443/tcp 3. Clearing Stale Routes (Optional) If old project mappings or stale ephemeral ports remain in your routes table, you can wipe the user-level routes file: rm -f \"$HOME/.portless/routes.json\" 4. Restarting the Proxy with Synchronized State Restart the proxy daemon passing the explicit user state directory: sudo PORTLESS_STATE_DIR=\"$HOME/.portless\" portless proxy Terminal output confirming clean restart: [portless:proxy] Initializing reverse proxy on ports 80 (HTTP) and 443 (HTTPS)... [portless:proxy] Watching state directory: $HOME/.portless [portless:proxy] Loaded 0 active routes from $HOME/.portless/routes.json ✔ Proxy is running and listening for incoming localhost traffic. Summary of Key Takeaways Explicit appPort: Because Firebase Hosting emulator binds to a specific configured port (default 5000, or custom e.g. 5002), declare \"portless\": { \"name\": \"<project>\", \"appPort\": <port> } in package.json. Speed Up Startup: Use --only hosting in your npm run dev script to avoid launching unnecessary emulators and prevent proxy timeouts. Environment Alignment: Set PORTLESS_STATE_DIR=\"$HOME/.portless\" to ensure smooth inter-process routing between the privileged proxy daemon and user CLI sessions. Clean Restarts: Use sudo portless stop or sudo pkill -f \"portless proxy\" followed by restarting with the explicit PORTLESS_STATE_DIR to reload altered configurations. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'portless-with-firebase-emulators.html'; this.page.identifier = '9271ed1c97_idportless-with-firebase-emulators'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "posh-windows-powershell.html": {
    "href": "posh-windows-powershell.html",
    "title": "PoSh - Windows PowerShell - Get Blogged by JoKi",
    "summary": "PoSh - Windows PowerShell Copy Markdown View Markdown GitHub Markdown Source PoSh - Windows PowerShell Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'posh-windows-powershell'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'posh-windows-powershell'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'PoSh - Windows PowerShell') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-30 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); MUAHHAUHUHUAUH!!! Ich werf mich weg! \uD83D\uDE01 PoSh - Microsoft reanimates Spice Girls? Also, inzwischen glaube ich echt, dass man einen gewissen Touch haben muss, um in der Marketingabteilung bei Microsoft USA arbeiten zu dürfen. Worum geht's? Um die glorreiche, nein schon fast himmeljauchzende Umbenennung der kommenden Microsoft Shell - Codename Monad, die nun seit einigen Tagen in Windows PowerShell von der Marketingfraktion in Redmond umgetauft wurde. Leute, leute, mir schmerzt der Bauch vor Lachen. Sorry, liebe Microsofties, aber das ist das beste Highlight bisher. Nicht nur wegen der Tatsache, dass der Name hüstel extremst bescheiden ist und klingt. Nein... es gibt bereits ein fast gleichnamiges Produkt und das bereits seit Jahren - zumindest PowerShell: PowerShell is a terminal emulator for the X11 Window System. If you end up having so many xterms open that you can never find the one you're looking for, PowerShell is for you. It supports many terminal \"windows\" embedded into a single X11 window, with each one given its own \"notebook\" tab. Ansonsten finde ich den Prompt noch spassiger: PoSh C:> Mein erster Gedanke ging in Richtung Spice Girls, der zweite landete beim LEO dictionary: posh adj. fesch, nobel, piekfein, schick, todschick, vornehm Hachja... herrlich. Man kann die Jungs vom Entwicklungsteam der Microsoft Shell einfach nur bedauern, aber das haben sie nun wirklich nicht verdient. Die MSH ist absolut powerful, außer Frage, aber mit solch einer Marketingaktion zieht Microsoft IMHO nur Lacher und Gelächter für das Produkt auf seine Seite. Nun gut, die Bauchmuskulatur entkrampft sich wieder. Für aktuelle Informationen zur Windows PowerShell \uD83D\uDE0E sind folgende Ressourcen auf alle Fälle einen Blick wert: Windows PowerShell Windows PowerShell RC1 (for .NET Framework 2.0 RTM) x86 Windows PowerShell RC1 Documentation Pack Neee, neee, neee... Hachja, herrlich, snief Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'posh-windows-powershell.html'; this.page.identifier = '9271ed1c97_idposh-windows-powershell'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "powershell-1-0.html": {
    "href": "powershell-1-0.html",
    "title": "PowerShell 1.0 - Get Blogged by JoKi",
    "summary": "PowerShell 1.0 Copy Markdown View Markdown GitHub Markdown Source PowerShell 1.0 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'powershell-1-0'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'powershell-1-0'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'PowerShell 1.0') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-11-16 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); RSS Feeds machen echt Laune... man ist ständig auf dem Laufenden und wird aktiv informiert. Inzwischen ist die Microsoft PowerShell aka Monad in der Version 1.0 offiziell veröffentlicht: Download bei Microsoft. Fein, ziehen, installieren und wohlfühlen. Pustekuchen! Auf der Downloadseite steht zwar der nette Hinweis, dass... aber sind die Setup-Scripter bei Microsoft nicht in der Lage so etwas Peinliches wie das hier zu vermeiden? *Windows PowerShell(TM) 1.0 Setup Error Cannot install this version of the product. You must first remove any earlier versions of Windows PowerShell(TM) 1.0 before you can install this version. Please click OK to close the Setup Wizard. Next open the Add/Remove Programs Control Panel, uninstall any versions of the Windows PowerShell(TM) 1.0, and run the Windows PowerShell(TM) 1.0 setup again.* Für mich steht da übersetzt folgendes: Lieber Anwender, schön dass du zu doof bist und Vorabversionen unserer Produkte einsetzt. Leider sind wir bei Microsoft ebenfalls unfähig, unsere Installationsroutinen für dieses Szenario anzupassen. Leute, Leute... So etwas in absolut inakzeptabel und oberpeinlich. Besonders da die Vorversion unter dem Programmnamen Windows Command Shell in der Softwareliste steht. Jeder andere ISV kann es besser. Shame on you, Microsoft! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'powershell-1-0.html'; this.page.identifier = '9271ed1c97_idpowershell-1-0'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "practical-postgresql.html": {
    "href": "practical-postgresql.html",
    "title": "Practical PostgreSQL - Get Blogged by JoKi",
    "summary": "Practical PostgreSQL Copy Markdown View Markdown GitHub Markdown Source Practical PostgreSQL Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'practical-postgresql'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'practical-postgresql'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Practical PostgreSQL') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-05-10 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Immer wieder funny, was sich in den Weiten des Internets findet: https://www.commandprompt.com/ppbook/ Leider nur für die älteren Versionen, aber dennoch aktuell. Müsste man mal sehen, ob's davon ein eBook und/oder eine PDF-Variante gibt. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'practical-postgresql.html'; this.page.identifier = '9271ed1c97_idpractical-postgresql'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "pragmatic-version-control-using-subversion.html": {
    "href": "pragmatic-version-control-using-subversion.html",
    "title": "Pragmatic Version Control Using Subversion - Get Blogged by JoKi",
    "summary": "Pragmatic Version Control Using Subversion Copy Markdown View Markdown GitHub Markdown Source Pragmatic Version Control Using Subversion Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pragmatic-version-control-using-subversion'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'pragmatic-version-control-using-subversion'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Pragmatic Version Control Using Subversion') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-29 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es gibt bereits einige Bücher zu Subversion (SVN) aber dieser Titel geht einen anderen Weg. Angefangen mit einer kurzen Erläuterung, was denn Versionskontrolle überhaupt bedeutet, beschreibt Mike Mason sehr detailiert die notwendigen und empfohlenen Schritte zur Realisierung gewisser Problemstellungen im täglichen Umgang mit Subversion. Anhand vieler Beispiele und einleuchtender Erläuterungen lässt sich Subversion schnell und nutzbringend als Versionskontrolle einsetzen. Die komplette Rezension ist wieder einmal bei Amazon veröffentlicht: https://www.amazon.de/exec/obidos/ASIN/0974514063 Viel Spass beim Lesen - der Rezension und des Buches. \uD83D\uDE09 Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'pragmatic-version-control-using-subversion.html'; this.page.identifier = '9271ed1c97_idpragmatic-version-control-using-subversion'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "preparations-for-devconmru-2015.html": {
    "href": "preparations-for-devconmru-2015.html",
    "title": "Preparations for Developers Conference 2015 - Get Blogged by JoKi",
    "summary": "Preparations for Developers Conference 2015 Copy Markdown View Markdown GitHub Markdown Source Preparations for Developers Conference 2015 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'preparations-for-devconmru-2015'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'preparations-for-devconmru-2015'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Preparations for Developers Conference 2015') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2015-02-20 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The First of its Kind in Mauritius Developers Conference 2015 is the first event in Mauritius, maybe even in the Indian Ocean which is organised as a \"classic\" conference. Yes, there have been various vendor-specific bootcamps in the past but never anything like this. \"From craftsmen - For craftsmen\" This year's conference is first time organised by the Mauritius Software Craftsmanship Community (MSCC) in partnership with a number of local and international companies. Although the MSCC was founded back in 2013 it quickly became clear that our tropical island has a certain lack of informational and technical events. During some monthly meetups we spoke about this situation and that it would be very interesting and delightful to organise such an event. Also, the monthly meetings of the MSCC are usually topic-centered and most amazingly we had solid technical information and good presenters - even though none is a professional trainer - during the last couple of month. The Developers Conference is just the consequent development of this process - our thirst for more information in the world of modern IT. Mauritius has been branded \"Cyber Island\" in the Indian Ocean... Opinions in those matters vary but with this conference we strive to improve the general attribution of our island. Mauritius has great political stability and economical advantages for foreign investors, and the most precious resource Mauritius has to offer is people's knowledge. The ICT sector in Mauritius is growing since years and maturing as the fourth pillar of our economy. With its geographical position Mauritius is also welcome as a business and knowledge hub between Africa and Asia. Welcome to the first ever Developers Conference in Mauritius! Extras & Specialties - It's not all about sessions Apart from the technical sessions and presentations during the day the conference is going to be complemented with additional activities of different types. Our venues provide lots of space for some extra fun... Stroll around and get some inspiration from our side activities. Socialise with other attendees offline and seize the opportunity to get some ideas for DIY projects from others. Global Azure Bootcamp Again! In April of 2013 we held the first Global Windows Azure Bootcamp at more than 90 locations around the globe! In March 2014 we topped that with 136 locations! This year we are again doing a one day deep dive class to help thousands of people get up to speed on developing Cloud Computing Applications for Azure. In addition to this great learning opportunity we will have another set of hands on labs. Hack-a-thon - Programming is a lifestyle Tired of listening and talking? Let the source code fly! The Developers Conference is a great chance to show your programming skills. Team up with other developers and solve a variety of challenging tasks. There will be different categories of competitions and we will organise different prices for the winners and runner-ups. A hack-a-thon is not only fun but also a solid way to put your skills on the table. Be part of it... Touch-typing Contest - Show us your keyboard skills Working with computers is bound to typing your source code, specifications or any kind of documentation via keyboard since decades. New alternatives like speech recognition are still in their early phase of development and practical use. Which leads us ultimately to the following questions: Do you know about touch-typing or speedtyping? How good are your skills? Show us and other participants your impressive speedtyping skills and compete among each other in various competitions and races. The typing speed is usually measured in words-per-minute (WPM), and a word is standardized to five characters or keystrokes. Ubuntu 15.04 - Celebrating Vivid Vervet The next version of Ubuntu is highly anticipated with its continuous development towards an unified user experience on the desktop, on the server, in the cloud, or on the mobile platform. Welcome the Internet of Things (IoT). Mark Shuttleworth introduced the new version Ubuntu and its foray into the mobile space some months back: \"This is a time when every electronic thing can be an Internet thing, and that’s a chance for us to bring our platform, with its security and its long term support, to a vast and important field. In a world where almost any device can be smart, and also subverted, our shared efforts to make trusted and trustworthy systems might find fertile ground.\" Bring your laptop or USB pendrives to receive a copy of the latest incarnation of Ubuntu, and step into a world of open source. DIY projects - Raspberry Pi, Arduino, any... You are more of a hardware geek? You like to pull up your sleeves and get cracking with single-board computers (SBC) like the Raspberry Pi, the Arduino, or other models? Then the maker space will be the right place to demo your Do-It-Yourself (DIY) projects to the rest of the audience. Learn the latest trends about small hardware projects and get some inspiration from others. Networking - Communication and exchange with others Communication with your environment is an essential part of everyone's life. And it doesn't matter whether you are actually living in a rural area in the middle of nowhere, within the pulsating heart of a big city, or in my case on a wonderful island in the Indian Ocean. The ability to exchange your thoughts, your experience and your worries with another person helps you to get different points of view and new ideas on how to resolve an issue you might be confronted with. Take the chance to exchange with other attendees in the reception area or on the balcony. Have some interesting conversations about software development, latest information gained during one of the presentations and let others know about your ups & downs in your projects. Also, join the Mauritius Software Craftsmanship Community and be part of the local IT community. The MSCC is a community for those who care and are proud of what they do. For those developers, regardless how experienced they are, who want to improve and master their craft. It is a community for those who believe that being average is just not good enough. Join the community and stay informed. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'preparations-for-devconmru-2015.html'; this.page.identifier = '9271ed1c97_idpreparations-for-devconmru-2015'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "presentation.html": {
    "href": "presentation.html",
    "title": "Presentations - Get Blogged by JoKi",
    "summary": "Presentations Copy Markdown View Markdown GitHub Markdown Source Presentations Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'presentation'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'presentation'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Presentations') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2009-12-14 - Filed under Personal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Sessions Mauritius Software Craftsmanship Community (MSCC) 11.2013 Linuxfest 2013 Running Windows applications on Linux - Taking into consideration that the End of Support Lifecycle for Microsoft Windows XP is closing in one might be confronted with the question whether it would be possible to keep existing hardware and installed software applications without having the necessity to upgrade the operating system (which eventually might be incompatible or requires higher investment). The session provides a brief overview of potential solutions and demonstrates quickly how to install and run Windows software on Linux. 11.2013 Infotech 2013 Running Windows applications on Linux - Taking into consideration that the End of Support Lifecycle for Microsoft Windows XP is closing in one might be confronted with the question whether it would be possible to keep existing hardware and installed software applications without having the necessity to upgrade the operating system (which eventually might be incompatible or requires higher investment). The session provides a brief overview of potential solutions and demonstrates quickly how to install and run Windows software on Linux. Microsoft Visual FoxPro Entwicklerkonferenz - Developer conference 11.2006 D-CLR - CLR Host für Visual FoxPro Die Common Language Runtime (CLR) des .NET Frameworks übernimmt die grundsätzliche Aufgabe zum Starten von Assemblies und Anwendungen in einer .NET Programmiersprache wie etwa C# oder Visual Basic. Weiterhin ist die CLR ebenfalls mit dokumentierten Programmierschnittstellen (APIs) ausgestattet, die es ermöglichen einen eigenen Host zu schreiben. Die bekanntesten CLR Hosts stammen derzeit von Microsoft: Internet Explorer, ASP.NET und SQL Server 2005. 11.2006 D-NET4 - NET4COM und ComProxyForNet NET4COM ist ein Teil von Sedna und bietet den Zugriff auf ausgewählte Klassen des .NET Frameworks über die COM-Schnittstelle. Somit können VFP-Entwickler die angebotenen Objekte in ihren eigenen Anwendungen nutzen und wieder ihren Kunden anbieten. Die Session zeigt die Voraussetzungen zur Nutzung, die aktuellen Features und Einschränkungen von NET4COM, und gibt Vorschläge für mögliche Alternativen. 11.2006 V-AFP - Active FoxPro Pages AFP 3.0 wurde gezielt auf Geschwindigkeit optimiert und ist um ein vielfaches schneller als AFP 2.4. Das echte Multithreading der AFP reduziert den Speicherbedarf erheblich, während ein ausgeklügeltes, verbessertes Sessionmanagement die problemlose Skalierbarkeit auf mehrere Server ermöglicht. Das AFP ControlCenter zeigt Ihnen alle wichtigen Informationen wie die Ausführungszeit oder die gerade bearbeitete Seite. PlugIns erlauben eine nahezu unbeschränkte Erweiterbarkeit der AFP. 11.2005 D-SVN - Versionskontrolle mit Subversion Versionskontrolle beschränkt sich nicht nur auf Softwareentwicklung und Quellcode. Mit diesen Worten steigen wir ein in die pragmatischen Vorgehensweisen zur Verwendung eines 'Zeitspeichers'. Auf Versionskontrolle sollte heutzutage kein Entwickler mehr verzichten - egal, ob die Projektentwicklung alleine oder im Team erfolgt. 11.2005 D-LINU - Linux Transfer, Aktueller Stand Multi-Plattform Zwei Welten treffen aufeinander... Einerseits unsere Erfahrungen in Visual FoxPro und andererseits steigendes Interesse der Kunden an Linux als Plattform in deren IT-Landschaft. Welche Probleme und Potentiale für VFP-Entwickler bestehen hierbei aktuell und zukünftig? Die Session gibt einen kurzen Statusbericht bzgl. der aktuellen Situation zwischen VFP 9.0 und Linux wider, zeigt Möglichkeiten für Interaktion mit den Systemen, und gibt Anregungen wie man durch Kenntnisse über Linux auf Anforderungen der Kunden reagieren könnte. 11.2005 D-REG - Reguläre Ausdrücke in VFP (und .NET) Schon mal Dupletten oder fehlerhafte Einträge aus den Datenbanken des Kunden isoliert? Ja? Nein? Macht nichts... Dann dürfte diese Session Ihnen Anregungen und Lösungen liefern, wie Sie sich die Arbeit vereinfachen können. Reguläre Ausdrücke (regular expressions) sind für VFP-Entwickler anfangs böhmische Dörfer, aber in anderen Programmiersprachen stellen sie ein mächtiges Werkzeug dar. 11.2005 D-GEIZ - Geiz ist geil! Kostenlose Tools... Nicht nur die Anschaffung der Entwicklungsumgebung kostet Geld, sondern auch weitere notwendige Zusatztools, Addons und Klassenbibliotheken fordern ihren Tribut. Seit etlichen Jahren gibt es sehr viele größere und kleinere Tools zur Unterstützung des Arbeitsablauf während der Entwicklung. Die Session umfasst einige Addons, die direkt in VFP genutzt werden können. 11.2005 D-XML - XML - Einführung und Überblick XML hat sich in den vergangenen Jahren von einem Modebegriff zu einer Sprache für Daten etabliert, die weit verbreitet ist und in vielen Anwendungsbereichen eingesetzt wird. Wie funktioniert XML, wofür lässt es sich alles einsetzen, und welche Möglichkeiten ergeben sich aus dessen Einsatz? Als Praxisbeispiel wird das Speichern und Laden von Konfigurationsdaten und Datenbanken als XML gezeigt. 11.2005 D-XSLT - XSLT - Konvertieren leicht gemacht Daten von einem Format in ein anderes zu konvertieren, ist heute trotz gängiger \"Microsoft-Formate\" wichtiger denn je - sei es die Ausgabe als HTML oder alternativ als PDF, die Ausgabe in eines der Office-Formate oder oder oder ... welche Möglichkeiten bietet XML im Zusammenspiel mit XSLT, Daten zu transformieren? Als Praxisbeispiel wird gezeigt, wie eine Datenbank grafisch erstellt und das Ergebnis mittels XSLT in ein SQL-Skript umgewandelt werden kann. 11.2004 D-LINU - Visual FoxPro 9.0 und Linux Zwei Welten treffen aufeinander... Auf der einen Seite haben wir unser über die Jahre liebgewonnenes Entwicklungstool Visual FoxPro, aber auf der anderen Seite steigt das Interesse der Kunden an Linux als Nutzungsplattform in deren IT-Landschaft. Welche Probleme und Potentiale für VFP-Entwickler bestehen hierbei künftig? Die Session gibt einen kurzen Statusbericht bzgl. der aktuellen Situation zwischen VFP 9.0 und Linux wider, zeigt Möglichkeiten für Interaktion mit den Systemen, und gibt Anregungen wie man durch Kenntnisse über Linux auf Anforderungen der Kunden reagieren könnte. Abschliessend werden wir uns auf einen kleinen Exkurs in die Welt von 'Open Source' begeben. 11.2004 D-ADD - Additive Datenbanken zu Visual FoxPro MSDE, MS SQL und MySQL - Die Datenbankengine von Visual FoxPro ist für alltägliche Anforderungen Ihrer Kunden 'garantiert' das richtige Werkzeug. Jedoch entstehen auch Szenarien in denen VFP garantiert nicht mehr die erste Geige spielt. Dafür gibt es unterschiedliche Anlässe: der Kunde verwendet bereits einen SQL Server, fordert höhere Sicherheit im Intranet oder linuxbasierte Server sind im Einsatz... Damit ergeben sich für Sie als Entwickler verschiedene Fragen: Unter welchen Aspekten ist es sinnvoll, additive Datenbanken zu VFP zu verwenden? Welche Unterschiede und Besonderheiten haben die diversen RDBMS? Wie gestaltet sich die Installation und Konfiguration der Datenbanken und wie nutzt man diese mit Visual FoxPro? Sie werden in dieser Session einen kurzen Überblick erhalten. Danach können wir in eine Unterhaltung mit Erfahrungswerten und Problemlösungen übergehen. 11.2004 D-AFP1 - Webanwendungen mit Active FoxPro Pages erstellen In dieser Session lernen Sie die Erstellung und Implementierung von Webanwendungen mit den Active FoxPro Pages (AFP). Dabei werden Ihnen die grundsätzliche Technik und Funktionsweise der AFP erläutert und der allgemeine Aufbau einer Webanwendung vermittelt. Am Ende der Session werden wir gemeinsam eine kleine Site entworfen und implementiert haben. 11.2004 D-AFP4 - Erstellen eigener Plugins für Active FoxPro Pages Wir werden uns dazu die verschiedenen Möglichkeiten zurIntegration von Extensions in die Active FoxPro Pages anschauen, und besonders auf die Funktionsweise des Plugin-Managers eingehen. Basierend auf den gewonnenen Erkenntnissen werden wir in dieser Session ein Plugin entwerfen, implementieren und zum Einsatz bringen. 11.2003 D-LINU1 11.2003 D-LINU2 11.2002 V-AFP - Active Foxpro Pages AFP 3.0 wurde gezielt auf Geschwindigkeit optimiert und ist um ein vielfaches schneller als AFP 2.4. Das echte Multithreading der AFP reduziert den Speicherbedarf erheblich, während ein ausgeklügeltes, verbessertes Sessionmanagement die problemlose Skalierbarkeit auf mehrere Server ermöglicht. Das AFP ControlCenter zeigt Ihnen alle wichtigen Informationen wie die Ausführungszeit oder die gerade bearbeitete Seite. PlugIns erlauben eine nahezu unbeschränkte Erweiterbarkeit der AFP. VFP Stammtisch Speyer - Monthly user group meetings 04.2006 Dynamic-Link Libraries 02.2006 Microsoft SQL Server 2005 - Neue Features und Verbesserungen (Stuttgart) Was bringt die neue Version des Datenbank-Flagschiffs aus dem Hause Microsoft für den Visual FoxPro Entwickler? Was gibt's an Neuigkeiten, Verbesserungen und Fallen? Welche Editionen samt Ausstattung gibt es? 11.2005 PostgreSQL 8.0 Seit der Version 8.0 unterstützt PostgreSQL direkt die Windowsplattform und steht als leistungsfähiges Datenbanksystem für die Entwicklung und Distribution von Anwendungen zur Verfügung. 08.2005 Versionskontrolle mit Subversion Den ersten Teil gestaltet Golo Haas mit allgemeinen Aspekten zu Versionskontrolle - welche Techniken und Tools es gibt. Insbesondere bei Verwendung von Subversion als Kontrollsystem. Im zweiten Part geht Jochen Kirstätter auf die Integration und Verwendung von Subversion in Visual FoxPro ein und zeigt, welche Möglichkeiten und Hindernisse hierbei bestehen. 07.2005 Microsoft SQL Server 2005 - Neue Features und Verbesserungen Was bringt die neue Version des Datenbank-Flagschiffs aus dem Hause Microsoft für den Visual FoxPro Entwickler? Was gibt's an Neuigkeiten, Verbesserungen und Fallen? Welche Editionen samt Ausstattung gibt es? 06.2005 Sie kennen Lego™? - Bausteine in Software Komponenten spielen in der modernen Softwareentwicklung eine große Rolle, indem sie wiederverwendbare Bausteine liefern, aus denen sich komplexe Applikationen zusammenstellen lassen. 05.2005 MSDN Webcast: Visual Studio 2005 mit Visual FoxPro 03.2005 Blob, Varchar, Binary - Feldtypen in Visual FoxPro 9.0 Mit Visual FoxPro verfügt die Datenbank über verbesserte und neue Feldtypen. Der Abend beschäftigt sich mit den Änderungen und zeigt Beispiele für mögliche Einsatzzwecke. Außerdem ist die SQL-Syntax in VFP 9.0 weiter an die ANSI-Empfehlungen angenähert worden. 02.2005 XML – Aufbau, Sinn und Zweck - Nutzbarkeit in VFP Die eXtensible Markup Language (XML) gibt's nun seit einigen Jahren und die Wandlung vom Hype zur alltäglichen Nutzung ist vollzogen. Das Einsatzgebiet erstreckt sich von Webseiten, Austausch von Daten bis hin zu kompletten Datenbanksystemen. 12.2004 VFP Anwendungen vertreiben - Windows Installer Deployment oder auch als Auslieferung bekannt, kann unterschiedlich aufgefasst werden. Das reicht vom simplen Kopieren von CD auf Festplatte bis zur komfortablen Installationsroutine mit erklärenden Einzelschritten und automatischer Systemkonfiguration. 08.2004 Additive Datenbanken zu Visual FoxPro - MSDE, MS SQL und MySQL - Unter welchen Aspekten ist es sinnvoll, additive Datenbanken zu VFP zu verwenden? Welche Unterschiede und Besonderheiten haben die diversen RDBMS? Wie gestaltet sich die Installation und Konfiguration der Datenbanken und wie nutzt man diese mit Visual FoxPro? 06.2004 Visual FoxPro 9.0 Beta - Features und Highlights Der Abend wird sich zentral um die aktuelle Beta von Visual FoxPro 9.0 drehen. Dabei geht es vorallem darum, die neuen Features, Funktionalitäten und Verbesserungen gegenüber den Vorgängern zu zeigen. Der Abend wird sicherlich nicht alle Bereiche abdecken können, aber gehen wir einfach davon aus, daß es sehr informativ werden dürfte. Articles O'Reilly 08.2005 C# Essentials, 2nd Edition Amazon 09.2006 Zeitmanagement mit Microsoft Office Outlook Wer dem Chaos auf seinem Schreibtisch Herr werden möchte, ist mit diesem Buch bestens beraten. 09.2006 Arbeiten mit dem Visual Studio 2005 Team System Ich habe bisher selten ein Fachbuch gelesen, welches diese klare Strukturierung in den Kapiteln und verständliche Formulierung aufweist, wie das Werk von Richard Hundhausen. 07.2005 Pragmatic Version Control Using Subversion Es gibt bereits einige Bücher zu Subversion (SVN) aber dieser Titel geht einen anderen Weg. Angefangen mit einer kurzen Erläuterung, was denn Versionskontrolle überhaupt bedeutet, beschreibt Mike Mason sehr detailiert die notwendigen und empfohlenen Schritte zur Realisierung gewisser Problemstellungen im täglichen Umgang mit Subversion. 07.2005 Introducing Microsoft SQL Server 2005 For Developers Als eines der ersten Bücher zu Microsoft SQL Server 2005, auch unter dem Codenamen \"Yukon\" bekannt, richtet sich das Buch primär an Entwickler, die bereits mit dem SQL Server 2000 arbeiten, und sich über die aufregenden Änderungen frühzeitig informieren möchten. Das Buch eignet sich ebenfalls für Datenbankadministratoren mit programmiertechnischen Ambitionen. 04.2005 .NET for Visual FoxPro Developers Ausgehend von der Situation eines VFP Entwicklers erläutert Kevin McNeish die grundsätzlichen Konzepte des .NET Frameworks. Basierend auf Visual Studio .NET (VS 2002) erklärt der Autor C# und Visual Basic .NET sowie deren Unterschiede zueinander. Ein herausragendes Merkmal ist, dass der Quellcode für die Beispiele immer in beiden Sprachen zur Verfügung steht. FoxX Professional Q4 2005 Introducing Microsoft SQL Server 2005 For Developers Webcast 05.2005 MSDN: Visual FoxPro mit Visual Studio 2005 Welche Datenbank verwendet man zur Erstellung einer Windows Forms Anwendung für den Desktop? SQL Server/MSDE stellt das Medium für die Enterprise-Entwicklung dar, aber für eine Desktopanwendung ist deren Deployment und Nutzung viel zu aufwendig. Der Webcast zeigt die Verwendung der Datenbank-Engine von Visual FoxPro 9.0 mittels OLE DB Provider. Für die Erstellung der Datenbank wird ActiveX Data Object Extension (ADOX) per COM Interop genutzt. Der Zugriff auf die Tabellen und Daten erfolgt per ADO.NET. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'presentation.html'; this.page.identifier = '9271ed1c97_idpresentation'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "privacy.html": {
    "href": "privacy.html",
    "title": "Privacy Policy - Get Blogged by JoKi",
    "summary": "Privacy Policy Copy Markdown View Markdown GitHub Markdown Source Privacy Policy Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'privacy'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'privacy'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Privacy Policy') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); Welcome to Get Blogged by JoKi (https://jochen.kirstaetter.name). As an independent software crafter and tech blogger, I respect your privacy and believe in full transparency regarding how information is collected, stored, and used on this website. 1. Overview & Core Philosophy This website is a static technical blog hosted on Firebase Hosting and compiled using DocFX and the GhostFx theme. I do not run database-backed user registration systems, user tracking profiles, or intrusive ad trackers. No personal identity information is required to read any articles, browse tags, download code samples, or access machine-readable resources (llms.txt, /raw/*.md). 2. Information Collected & Technologies Used Aggregated Web Analytics (Google Analytics 4) This site uses Google Analytics 4 (GA4) with measurement ID G-F1KD1511QR to understand aggregate readership patterns, popular topics, operating system/browser types, and referral sources. GA4 collects anonymised IP addresses and generic telemetry data. No personally identifiable information (PII) is sold or shared with third parties. Local Browser Storage To provide a smooth reading experience, the site uses standard browser localStorage solely for: Theme Preference: Remembering your chosen display theme (light, dark, or auto OS-synced mode) between page visits. No personal details or tracking identifiers are stored in localStorage. Service Worker & Offline Caching The site registers a client-side Service Worker (sw-v1.js) to cache static CSS, fonts, icons, JavaScript, and recently viewed articles. This enables fast offline reading and lower bandwidth consumption. All cached assets remain entirely within your local device browser sandbox. Third-Party Links & Social Sharing Articles on this site frequently link to external resources, such as GitHub repositories, official documentation, tech conference websites, and social media platforms (X, BlueSky, Mastodon, LinkedIn). When clicking external links or using sharing buttons, the respective third-party privacy policies govern your interaction. 3. Machine-Readable & AI Agent Access This website explicitly supports search engines, RAG systems, and autonomous AI agents: Machine-readable context and best-fit guidance are provided in /llms.txt and /llms-full.txt. Raw Markdown source for all articles is publicly available under /raw/<slug>.md and via Accept: text/markdown HTTP content negotiation. WebMCP tools (search_blog) allow client-side agent tool invocation without tracking. 4. Your Rights & Data Protection Under global privacy standards including the EU General Data Protection Regulation (GDPR) and similar frameworks, you have the right to browse anonymously, clear cookies or local storage at any time via your browser settings, and request information regarding data practices. 5. Contact Information If you have any questions, suggestions, or concerns regarding this privacy policy or data practices on this blog, please contact: Email: jochen@kirstaetter.name Website Contact: https://jochen.kirstaetter.name/contact.html Location: Flic en Flac, Mauritius"
  },
  "private-projects-added-at-least-started.html": {
    "href": "private-projects-added-at-least-started.html",
    "title": "Private projects added (at least started...) - Get Blogged by JoKi",
    "summary": "Private projects added (at least started...) Copy Markdown View Markdown GitHub Markdown Source Private projects added (at least started...) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'private-projects-added-at-least-started'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'private-projects-added-at-least-started'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Private projects added (at least started...)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-03-19 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); I couldn't stop... There were still some tweaks and fine-tunings to do on this blog after I uploaded the new template and CSS files. But for the moment, I'm okay with the current visual outfit of this blog. After I finished with the re-design of my blog, I wanted to add some more content and so you get it. Have a look at the new Projects section of this web site. There you will get an overview of my pending private projects, like COM Proxy For .Net, AfpWiki and others. For sure, this project list is going to be completed as soon as I will have more time to add content. At the moment I think, it is most interesting to see that the hyperlinks to download my components are available. So, please stay tuned, there are more things to come during the next couple of days/weeks. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'private-projects-added-at-least-started.html'; this.page.identifier = '9271ed1c97_idprivate-projects-added-at-least-started'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "produktreife-nhert-sich.html": {
    "href": "produktreife-nhert-sich.html",
    "title": "Produktreife nähert sich... - Get Blogged by JoKi",
    "summary": "Produktreife nähert sich... Copy Markdown View Markdown GitHub Markdown Source Produktreife nähert sich... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'produktreife-nhert-sich'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'produktreife-nhert-sich'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Produktreife nähert sich...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-03-25 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nachdem ich ja bereits über Nichts ist unmöglich... geplaudert hatte, ging es heute Morgen mit den ersten Schritten der Verfeinerung weiter. Zunächst einmal war Aufräumen angesagt. Etlichen Testcode, unterschiedliche Projekte in unterschiedlichen Solutions bereinigt und so weiter... Achja, und Vereinheitlichung auf ein .NET Framework... \uD83D\uDE09 - Irgendwie hatte ich ein heiles Chaos in meinem Entwicklungsbaum. Naja, auch nicht weiter schlimm. Neue Verzeichnisse, neue Solution, frische Projekte und dann Pi mal Daumen die einzelnen Klassendateien reingewürfelt. Die Referenzen neu gesetzt, ein paar Errormessages aus dem Weg programmiert und tada... Ein nahezu fertiges, deploybares Produkt. Fein, es macht Spass, wenn die normale Arbeitsweise unter Visual FoxPro ebenfalls im .NET Framework gefahren werden kann. Da ich inzwischen eine kompontenorientierte Splittung meiner Codefragmente vollzogen habe, kann ich mir auch mal Gedanken zu Strong Names (SN), dem Global Assembly Cache (GAC) und den dazugehörenden Tools machen. Hehe, das theoretische Wissen habe ich ja bereits durch einiges an Literatur, nunja, mal sehen, ob es sich auch so einfach in der Realität umsetzen lässt, wie es die Werbung verspricht. Die Aufteilung der Klassen in Assemblies war eigentlich ziemlich easy und straight forward. Ich habe aktuell drei Projekte: Connection Library HttpHandler Windows Client Die Connection Library repräsentiert hierbei die Grundlage für die beiden anderen Projekte wie auch für künftige Clients. Und hierfür brauche ich noch den Strong Name und die Erfahrung im Umgang mit dem GAC. Ich hatte mich mal ansatzweise vor einigen Jahren damit beschäftigt, als mein Kollege Boas Probleme bei der Auslieferung seines Crypto.NET Plugins für die Active FoxPro Pages hatte, aber erstens ist das schon einige Zeit her und zweitens könnte sich ja wieder was geändert haben. Wir werden sehen... Naja, soweit mal der aktuelle Stand der Dinge... Insgesamt gibt's noch ein paar Ideen, die ich auf alle Fälle umsetzen möchte: Konfiguration für Connection Library duale Frameworkunterstützung - also 1.1 und 2.0 COM Interface Dokumentation weitere Frontends (SmartClient, andere Sprachen und so weiter...) Achja, die ersten praktischen Experimente mit ClickOnce-Deployment sind im Zusammenspiel mit dem Windows Forms Projekt auch gelaufen... Und ich konnte den Client erfolgreich installieren. War ganz witzig... Mehr dazu wird's potentiell während der nächsten Wochen geben. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'produktreife-nhert-sich.html'; this.page.identifier = '9271ed1c97_idproduktreife-nhert-sich'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "programming-windows-8-apps-with-html-css-and-javascript.html": {
    "href": "programming-windows-8-apps-with-html-css-and-javascript.html",
    "title": "Programming Windows 8 Apps with HTML, CSS, and JavaScript - All you need in one title - Get Blogged by JoKi",
    "summary": "Programming Windows 8 Apps with HTML, CSS, and JavaScript - All you need in one title Copy Markdown View Markdown GitHub Markdown Source Programming Windows 8 Apps with HTML, CSS, and JavaScript - All you need in one title Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'programming-windows-8-apps-with-html-css-and-javascript'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'programming-windows-8-apps-with-html-css-and-javascript'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Programming Windows 8 Apps with HTML, CSS, and JavaScript - All you need in one title') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-05-20 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); It took me a while to work through the 800+ pages of this title. And yes, I really mean working not reading... Since the release of Windows 8 it should be obvious to any Windows software developer that there are new ways to develop, deploy and market applications for a broader audience. Interestingly, Microsoft started to narrow the technological gap between the various platforms - desktop, web, smartphone and XBox - and development of modern apps with HTML, CSS and JavaScript couldn't be easier. Kraig covers all facets of modern Windows 8 apps from the basic building blocks and project templates in Visual Studio 2012 over to the thoughtful use of specific APIs to finally proper deployment in the App Store and potential monetization. The organisation of the book is lied out like step by step instructions or a tutorial. Kraig literally takes the reader by the hand and explains in detail in his examples about the reasons, the pros, and the cons of a certain way of implementation. Thanks to cross-references to other chapters he leaves the choice to the reader to dig deeper right now or to catch up at some time later. Personally, I have to admit that I really enjoyed the relaxed writing style. App development is not dust-dry rocket science and it should be joyful to learn about new technologies. And thanks to the richness of the various chapters and samples you could easily adapt and transfer the knowledge gained in this title to other platforms like Windows Phone 8. And last but not least: The ebook is freely available at Amazon, Microsoft Press and O'Reilly. Don't think about it, just get the book. Now. Update: I already mentioned this title in other blog entries which are related to Microsoft certification. Feel free to read on and to discover more online resources: Learning content for MCSDs: Web Applications and Windows Store Apps using HTML5 More content for MCSDs: Web Applications and Windows Store Apps using HTML5 O'Reilly offers free webcasts on their site, too. And in case that you would like to know more about Kraig's book and his experience with various development teams, please checkout this one: Zero to App in Two Weeks: Programming Windows 8 Apps in HTML, CSS, and JavaScript. The recording should be available soon. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'programming-windows-8-apps-with-html-css-and-javascript.html'; this.page.identifier = '9271ed1c97_idprogramming-windows-8-apps-with-html-css-and-javascript'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "projekt-sunshine.html": {
    "href": "projekt-sunshine.html",
    "title": "Projekt - Sunshine - Get Blogged by JoKi",
    "summary": "Projekt - Sunshine Copy Markdown View Markdown GitHub Markdown Source Projekt - Sunshine Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'projekt-sunshine'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'projekt-sunshine'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Projekt - Sunshine') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-28 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Als Software-Entwickler gehen mir ständig irgendwelche Ideen für Konzepte und Programmierung durch den Kopf... ich nehme an, dass das zum Berufsrisiko gehört. So auch in den letzten Tagen. Es lässt sich einfach nicht vermeiden. Okay, da wir mit der aktuellen Projektentwicklung so ziemlich vor dem Abschluß stehen, und ich ab und zu mal ein wenig Abwechslung und Entspannung benötige, fröhne ich in meiner geringen Freizeit ein wenig dem Sport. Nicht sonderlich viel aber immerhin... Und so ergab es sich die Tage, dass ich einfach mal ein neues Projekt begonnen habe, um Daten meiner sportlichen Aktivitäten zu erfassen und später irgendwann einmal auch statistisch auswerten zu können. Wow, noch eine Software im Sinne eines 'Personaltrainers'. Hast du nicht gesehen... Well, ja, irgendwie in der Art soll es etwas werden, aber vielleicht auch mehr. Der Ausgang dieses Projektes ist offen... Das Interessante an diesem Projekt wird jedoch die Herangehensweise der Entwicklung sein. Denn obwohl oder gerade weil es in Visual FoxPro realisiert wird, möchte ich mit diesem Projekt ein persönliches Experiment angehen. Und zwar werde ich entgegen meiner üblichen Vorgehensweise diese Anwendung mal rein aus der Sicht des Anwenders aufbauen. Also, was sehe ich? Wie funktioniert es und wie fühlt es sich an. Ich meine, dass man solch eine Vorgehensweise 'Top-Down-Design' nennt, lasse mich aber gerne eines Besseren belehren. Okay, also ausgehend vom Grundsatz Hauptsache, es sieht gut aus! schreibe ich aktuell an der Gesamtoberfläche und an den visuellen Komponenten. Und in der Tat sieht die Software bereits schon ein wenig stylisch aus. Anbei mal ein Screenshot: Und damit sind wir sicherlich auch beim ersten Problem des Abends... Entwickler sind keine Designer, und wirklich gut aussehende Grafiken, Icons und Pictures sind auch in den Weiten des Internets schwer zu bekommen, zumindest wenn man sehr genaue Vorstellungen für das Gesamtbild der Anwendung hat. Falls sich also jemand oder auch mehrere Grafiker/Designer bereit erklären möchten, dem Projekt ein wenig was beizusteuern. Gerne, schreibt mir eine Mail, skypt mich an, etc... Bei der Betrachtung der technischen Details möchte ich für die Entwicklung des Projekts folgende Rahmenpunkte abstecken: Visual FoxPro 9.0 Service Pack 1 Acodey Komponentenbibliothek - die eigentliche Power im Projekt GDIPlusX aus dem VFPX Community Projekt freie Grafiken und Icons unzählige Blogartikel mit Ideen, Anregungen und Lösungen Das Datenbankdesign werde ich erst zu einem späteren Zeitpunkt angehen, denn aktuell schwirren meine Gedanken noch viel zu sehr durch den Kopf. Das wird sich mit der Zeit legen und dann folgt das ERM. Die Entscheidung für eine bestimmte Datenbank obliegt mir Dank Komponenten aus Acodey nicht. Dennoch wird der VFP DBC als erster Storage zum Einsatz kommen. Für die Ausgabe von Auswertungen werde ich in diesem Projekt auf die Fähigkeiten der VFP 9.0 Reportengine zurückgreifen. Aber später mehr dazu. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'projekt-sunshine.html'; this.page.identifier = '9271ed1c97_idprojekt-sunshine'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "quattro-stagioni-clip-day-two.html": {
    "href": "quattro-stagioni-clip-day-two.html",
    "title": "Quattro stagioni (CLIP Day Two) - Get Blogged by JoKi",
    "summary": "Quattro stagioni (CLIP Day Two) Copy Markdown View Markdown GitHub Markdown Source Quattro stagioni (CLIP Day Two) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quattro-stagioni-clip-day-two'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quattro-stagioni-clip-day-two'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Quattro stagioni (CLIP Day Two)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-10-21 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); 5:45 Zimmer 330 - In the middle of the night Wenn man schon im Hotel übernachtet, dann muss man auch gleich aus den Vollen schöpfen? Herrje, was ein Glück waren meine Schlafübergangsphasen extrem kurz. Ins Bett und weg in den Tiefschlaf und jetzt flupp raus dem Tiefschlaf ab ins Bad. Woah, was ist denn das für'n Zombie im Spiegel? Okay, wenn man schon mal rumsteht, schaden sicherlich ein paar Spritzer Wasser auch nicht. Achja, Wasser - da war doch was... grübel Und schon vernehme ich eine Stimme: \"Los, wir müssen ins Schwimmbad\" - Inzwischen hat sich der Verursacher gemeldet. - 6:00!! - um die Zeit gehe ich zuhause (nach einer Hacking-Night) eigentlich erst schlafen, aber okay, ich hab's mir ja selbst eingebrockt. Noch die Anti-Rutsch-Baumwollsocken angezogen, den korrekten Sitz der Kleidung überprüft und ab mit Golo zwei Etagen tiefer Weckdienst für Alex spielen. 6:15 Hotelschwimmbad - platsch Vielleicht sollte die Hotelleitung zusätzlich zu \"GARAGE\" auch noch \"Schwimmbad\" auf die Durchgangstür schreiben. Nunja, man kann nicht alles haben. Wenigstens sind die Umkleiden mollig warm und der Anblick des Pools entschädigt auch für die morgendlichen Strapazen. Wieso bin ich nochmal mitgegangen? Egal, nach typischer Liegestuhlreservierungsmanier geht's ab ins Nass. Herrlich, das tut gut und aktiviert den Kreislauf - und die gemessenen 29° Celsius sind auch okay. Eisbären dürfte es zu warm sein, aber Delfine fühlen sich darin wunderbar. Wie's mit Tigern aussieht... dunno. 7:40 Das Telefonat Nach einer genüsslichen Stunde in der großen Badewanne und ein wenig Relaxen auf den Rattanliegestühlen tappen wir wieder auf die Zimmer - Heiß duschen! Sowohl Komfort muss sein, denn bedingt durch die Uhrzeit bin ich immer noch nicht wach. Danach heisst es Schlachtplan für den heutigen Tag einleiten... ringring* - \"Und fit?\" - \"mampf, joooah, gerade beim Frühstücken...\" - \"Okay, wann bist etwa da?\" - \"Pünktlich um neun...\" - fein, die Organisation steht noch. Zeit für das eigene leibliche Wohl zu sorgen. Auf dem Weg in den Speiseraum unterwegs wieder Alex in die Karawane eingereiht und da war es wieder - das Raucher/Nichtraucher-Problem !) Wir haben uns dann doch für den Nichtraucherbereich entschieden (auch wenn es in Konsequenz einen längeren Weg zum Buffet bedeutete). In diesem Zuhang ein Lob ans Hotel - klasse Auswahl. Wir wurden sitt und satt. 8:50 Die Eskorte des Exilanten Nach der Mahlzeit bedarf es einen Verdauungsspazierganges. Also, ab zur S-Bahnhofbaustelle (daß wir uns heute nacht nicht die Haxen gebrochen haben, grenzt schon fast an ein Wunder...) und warten... Mit etwa 10 Minuten Verspätung ist mit Thomas \"Willi\" Wilting das Quattro stagioni für den heutigen Tag vollzählig. Auf dem Rückweg werden noch die letzten Details unseres Schlachtplans besprochen. 9:20 CLIP und \"unser Auftritt\" Im Formatsflug geht's in die CLIP Räumlichkeiten des Tages. Wir fühlen uns wohl, sehen sofort viele bekannte Gesichter und erste Gespräche untereinander starten - phantastisch! Unsere eigens für dieses Treffen gestylten T-Shirts erhalten durchweg positive Resonanz, es hat geklappt. Im Laufe des gesamten Tages werden wir noch öfters darauf angesprochen und denn die Shirts erhältlich wären. Hier bleibt noch zu klären, inwiefern an authentifizierte CLIP-Mitglieder ebenfalls Shirts gegeben werden dürfen. Wäre ziemlich cool, wenn wir ein Team zusammenbringen würden. Hmmmm, grübel - Wäre es nicht angenehm, wenn man im Sommer 2005 ein \"sportliches\" Event veranstalten würde? Sozusagen Kicker-Meisterschaften oder sowas? 10:15 Microsoft Office Die Begrüßung aller Anwesenden (Microsoft, CLIP Members und MVPs) durch Richard ist von starken Ausfällen (mit verständlichen Gründen) anderer Microsoft Mitarbeiter begleitet. Unter anderem wird auf die strategisch im Raum verteilten Standtische mit Schild für die jeweiligen Microsoft Technologien hingewiesen. Der Bereich Office bleibt unbesetzt - die Füchse unter den Anwesenden wittern ihre Chance... flupp und schon wird kurzerhand der Office-Sektor von Visual FoxPro Entwicklern übernommen. Hehehe, Totgesagte leben länger! In diesem Zusammenhang sei auf die Initiative von Jürgen Wondzinski und Armin Neudert hingewiesen. Networking mit Pausen Im Verlauf des Tages wurde die Möglichkeiten des Gespräch untereinander immer wieder durch Pausen, äh, Sessions zu unterschiedlichen Themen innerhalb von Microsoft wie auch der Community unterbrochen. Alles absolut professionell und informativ. Ich hab' sehr viel dabei mitgenommen und einige Aspekte werden bei meiner Planung 2005 mehr Aufmerksamkeit erfahren. Ich nehme an, daß der Spruch des Tages, wenn nicht sogar des Get Together durch Golo \"Eisbär\" Haas gesetzt wurde: \"Externe Verlinkung auf meiner Community - Nö, lass mal.\"). Es bleibt zu hoffen, daß einige der Teilnehmer die vorgestellte Successstory als positiven Beispiel nehmen und miteinander Erfolge zustande bringen, statt gegeneinander zu arbeiten. Es funktioniert, was sich anhand der deutschsprachigen FoxPro Usergroup (https://www.dfpug.de) eindeutig belegen lässt. Einige Gesprächsthemen außerhalb der Sessions waren sehr aufschlussreich (etwa \"Was ist ICQ?\"), andere wiederum recht vorsichtig (\"Active FoxPro Pages? Das ist doch das Tool mit Visual FoxPro, oder?\"), und wieder andere einfach erheiternd (\"Wir machen Community schon seit 12 Jahren.\" - \"Echt? Wow.\"). 15:00 Und das wär' ihr Preis gewesen Den Tag mit einem Gewinnspiel zu beginnen (Successstory von Alex und Golo) und abzuschliessen (Microsoft), ist eher ungewöhnlich. Dennoch eine gelungenen Abwechslung. Auch hier zeigte sich ganz klar die Überlegenheit und der Erfolg von Visual FoxPro - zwei von vier Preisen gingen an VFP-Vertreter... okay, kleiner Gag! Es war eine reguläre Ziehung mit Viviana als Glücksfee. 15:15 Let's CLIP together Mit Aussicht auf weitere Unterstützung, Förderung im Rahmen der Möglichkeiten und dem nächsten Treffen auf der CeBIT 2005 in Hannover klingt das Get Together genüsslich aus. Ich bzw. wir haben sehr viele Informationen und Möglichkeiten zur Gestaltung unserer Communities mit nach Hause nehmen können. Dafür möchte ich mich ganz herzlich bei Microsoft und den Verantwortlichen für diese Events ganz herrlich bedanken: Richard, Thomas, Birgit, Viviana, Florian und Evelyn - Danke! (versehentlich nicht Genannten gilt dieser Dank ebenfalls) 15:30 Die Reise geht weiter - Teil 1 Schnell noch Thomas zur S-Bahn gebracht. Danach ein kurzer Boxenstopp auf der Toilette und die mentale Vorbereitung auf die Heimfahrt. Aber öh, hallo? Golo, Alex, wo seid ihr? Spurlos verschwunden? Tjoa, alle weg - bis auf Verena. Okay, dann eben noch ein wenig Smalltalk bis die beiden wieder auftauchen, 16:00 Die Reise geht weiter - Teil 2 Birgit erlöst mich von den unendlichen Qualen des Wartens, und die beiden Helden folgen mir nun widerwillig?) ans Auto, um die Heimreise anzutreten. Offensichtlich gefällt's denn beiden sooo gut auf den CLIP Events, daß sie gar nicht mehr heim wollen. Viviana, vielleicht solltest du das beim nächsten Mal berücksichtigen... Während der Fahrt lassen wir die Eindrücke nochmals auf uns wirken. Die Ergebnisse sind in der CLIP Newsgruppe dokumentiert. Navigationssystem in Höchstform Wow, nach diesem Gesprächmarathon mit einer wahren Informationsflut ist unser humanes Navigationssystem G.O.L.O. immer noch top in Form - .o( ob heute einer dieser Tage ist? ). Wir sind gespannt. Und in der Tat schaffen wir es relativ zeitnah und reibungsfrei an der Abwurfstelle für Alex anzukommen. Alex, bis zum nächsten Mal. 20:00 Futtersuche in Karlsruhe Die Fortsetzung unserer Fahrt gestaltet sich ebenfalls recht angenehm, okay, stockender Verkehr um die Ballungszentren, aber keinerlei Stress... Dennoch stellt sich im Laufe des Abends ein leichtes Hungergefühl ein - JellyBeans zum Abendessen kommen nicht wirklich gut. Außerdem steht Tanken auf dem Speiseplan. Okay, schnell ist klar, daß nur ein Burger King derzeit in Frage kommen kann. Gesagt, gesucht und... doch endlich gefunden. .o( Mit dem nächsten G.O.L.O.-Update muss ich unbedingt die BK-Übersichtskarte mitführen. ) 21:20 Zwei Männer allein im Wald Frisch gestärkt geht's auf den letzten Streckenabschnitt für nach Hause. Dabei haben wir uns wie auf der Hinfahrt für die Landstraße über Johanniskreuz entschieden. Um die Uhrzeit noch kein Problem, dennoch grüßt uns freundlich ein kreuzender Fuchs - Fox Is Anywhere! - und ohne Zwischenfall erreichen wir die wohlverdienten Betten. Fazit: Was ein cooles CLIP Event - erneut bin ich begeistert und freue ich wirklich auf die nächste Möglichkeit der Zusammenkunft. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'quattro-stagioni-clip-day-two.html'; this.page.identifier = '9271ed1c97_idquattro-stagioni-clip-day-two'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "quickstart-use-gemini-with-net.html": {
    "href": "quickstart-use-gemini-with-net.html",
    "title": "Quickstart: Use Gemini with .NET (Google AI) - Get Blogged by JoKi",
    "summary": "Quickstart: Use Gemini with .NET (Google AI) Copy Markdown View Markdown GitHub Markdown Source Quickstart: Use Gemini with .NET (Google AI) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quickstart-use-gemini-with-net'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quickstart-use-gemini-with-net'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Quickstart: Use Gemini with .NET (Google AI)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-04-02 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); This quickstart shows you how to get started with the Gemini API using an SDK for .NET called Mscc.GenerativeAI. Prerequisites To complete this quickstart locally, ensure that your .NET development meets the following requirements: .NET 6+ or .NET Framework 4.7.2+ The NuGet package also supports .NET Standard 2.0. Set up your API key To use the Gemini API, you'll need an API key. If you don't already have one, create a key in Google AI Studio. Get an API key In order to keep private, sensitive information and secrets out of your source code repositories, it is recommended to use either Environment Variables, User Secrets, or a Key/Secrets Manager to retrieve data like an API key. Here, I'm going to create an .env file and place it into the project folder with the following content. GOOGLE_API_KEY=<The generated Gemini API key> To access the value of a variable that is defined in a .env file, use $dotenv. More details are described under Environment Variables in the official documentation. Set the file properties with a Build action of None and copy instructions of Copy, if newer. <None Update=\".env\" Condition=\"Exists('.env')\"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> Install the SDK / NuGet package The SDK for .NET for the Gemini API is contained in the Mscc.GenerativeAI package. Install the dependency using dotnet CLI: dotnet add package Mscc.GenerativeAI See README for alternative options, like the NuGet Package Manager. Initialize the Generative Model Before you can make any API calls, you need to add a reference to the namespace Mscc.GenerativeAI and initialize the Generative Model. using Mscc.GenerativeAI; var googleAI = new GoogleAI(apiKey: Environment.GetEnvironmentVariable(\"GOOGLE_API_KEY\"); var model = googleAI.GenerativeModel(model: Model.GeminiPro); Generate Text var response = await model.GenerateContent(\"Write a story about a magic backpack.\"); Console.WriteLine(response.Text); What's next Explore the README of the NuGet package which has more samples documented. All unit tests are accessible in the GitHub repository: GitHub - mscraftsman/generative-ai: Gemini AI Client for .NETGemini AI Client for .NET. Contribute to mscraftsman/generative-ai development by creating an account on GitHub.GitHubmscraftsman If you're new to generative AI models, you might want to look at the concepts guide and the Gemini API overview before trying a quickstart. Image credit: Gemini using prompt Create an image showing the fast start of a race in motor sport. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'quickstart-use-gemini-with-net.html'; this.page.identifier = '9271ed1c97_idquickstart-use-gemini-with-net'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "quickstart-using-gemini-with-net-vertex-ai.html": {
    "href": "quickstart-using-gemini-with-net-vertex-ai.html",
    "title": "Quickstart: Use Gemini with .NET (Vertex AI) - Get Blogged by JoKi",
    "summary": "Quickstart: Use Gemini with .NET (Vertex AI) Copy Markdown View Markdown GitHub Markdown Source Quickstart: Use Gemini with .NET (Vertex AI) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quickstart-using-gemini-with-net-vertex-ai'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quickstart-using-gemini-with-net-vertex-ai'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Quickstart: Use Gemini with .NET (Vertex AI)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2024-04-03 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); This quickstart shows you how to get started with the Gemini API in Vertex AI on Google Cloud using an SDK for .NET called Mscc.GenerativeAI. Prerequisites To complete this quickstart locally, ensure that your .NET development meets the following requirements: .NET 6+ or .NET Framework 4.7.2+ Account on Google Cloud The NuGet package also supports .NET Standard 2.0. Get set up on Google Cloud To get you started using Vertex AI, follow the instructions in the official guide. You need to complete the following steps: Set up a project Enable billing for the Google Cloud project Enable Vertex AI APIs Install the Google Cloud CLI Set up Application Default Credentials (ADC) In conclusion, you need the project ID associated with your project, and choose a supported region. In order to keep private, sensitive information and secrets out of your source code repositories, it is recommended to use either Environment Variables, User Secrets, or a Key/Secrets Manager to retrieve data like an API key. Here, I'm going to create an .env file and place it into the project folder with the following content. GOOGLE_PROJECT_ID=<your project ID> GOOGLE_REGION=us-central1 To access the value of a variable that is defined in a .env file, use $dotenv. More details are described under Environment Variables in the official documentation. Set the file properties with a Build action of None and copy instructions of Copy, if newer. <None Update=\".env\" Condition=\"Exists('.env')\"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> Install the SDK / NuGet package The SDK for .NET for the Gemini API is contained in the Mscc.GenerativeAI package. Install the dependency using dotnet CLI: dotnet add package Mscc.GenerativeAI See README for alternative options, like the NuGet Package Manager. Note: This is a quickstart to Gemini on Vertex AI. If you need additional authentication options see README of Mscc.GenerativeAI and consider to add the package reference Mscc.GenerativeAI.Google which is based on the .NET Cloud Client Libraries. Initialize the Generative Model Before you can make any API calls, you need to add a reference to the namespace Mscc.GenerativeAI and initialize the Generative Model. using Mscc.GenerativeAI; var vertexAI = new VertexAI( projectId: Environment.GetEnvironmentVariable(\"GOOGLE_PROJECT_ID\"), region: Environment.GetEnvironmentVariable(\"GOOGLE_REGION\")); var model = vertexAI.GenerativeModel(model: Model.Gemini10Pro); The necessary access token to authenticate against Google Cloud is retrieved from the Application Default Credentials or the Google Client Library, in case that you are using the Mscc.GenerativeAI.Google package. Generate Text var prompt = \"Write a poem about Japanese koi.\"; var responseStream = model.GenerateContentStream(prompt); // Use StringBuilder to append the streamed responses await foreach (var response in responseStream) { var response = await model.GenerateContent(); // optional, check for response.Candidates[0].FinishReason Console.WriteLine(response.Text); } Note: The package Mscc.GenerativeAI offers a unified developer experience across Google AI and Vertex AI provided by the interface IGenerativeAI. Meaning, the non-streaming method GenerateContent is available in Vertex AI and works as expected. What's next Explore the README of the NuGet package which has more samples documented. All unit tests are accessible in the GitHub repository: GitHub - mscraftsman/generative-ai: Gemini AI Client for .NETGemini AI Client for .NET. Contribute to mscraftsman/generative-ai development by creating an account on GitHub.GitHubmscraftsman If you're new to generative AI models, you might want to look at the concepts guide and the Gemini API overview before trying a quickstart. What's the difference from Google AI Gemini API The Vertex AI Gemini API and Google AI Gemini API both let you incorporate the capabilities of Gemini models into your applications. The platform that's right for you depends on your goals. The Vertex AI Gemini API is designed for developers and enterprises for use in scaled deployments. It offers features such as enterprise security, data residency, performance, and technical support. If you're an existing Google Cloud customer or deploy medium to large scale applications, you're in the right place. If you're a hobbyist, student, or developer who is new to Google Cloud, try the Google AI Gemini API, which is suitable for experimentation, prototyping, and small deployments. If you're looking for a way to use Gemini directly from your mobile and web apps, see the Google AI SDKs for Android, Swift, and web. Image credit: Gemini using prompt Create an image showing the fast start of a race in motor sport. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'quickstart-using-gemini-with-net-vertex-ai.html'; this.page.identifier = '9271ed1c97_idquickstart-using-gemini-with-net-vertex-ai'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "quoted-on-mva-voices.html": {
    "href": "quoted-on-mva-voices.html",
    "title": "Quoted on MVA Voices - Get Blogged by JoKi",
    "summary": "Quoted on MVA Voices Copy Markdown View Markdown GitHub Markdown Source Quoted on MVA Voices Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quoted-on-mva-voices'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'quoted-on-mva-voices'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Quoted on MVA Voices') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-07-04 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); A couple of weeks ago, I received an email from the Dean of Microsoft Virtual Academy (MVA) asking for permission to quote a statement I made during a jump start. Following is an excerpt from that request: \"Dear Jochen, I would like to thank you for providing insight as to how the Advanced HTML5 Jump Start helped you improve your skills. I mentioned this to the leadership team at MVA, and they were pleased to hear this so much that they would like your permission to use a quote from your email to me on the MVA website.\" Of course! I really enjoy those free MVA jump starts - live and later the recordings. Actually, I prefer the live ones because you really have a chance to communicate with the MVA studio team and the experts in the chat. Luckily, the live stream is provided in two quality levels and with the remote situation of Mauritius, I always have to switch to 'Standard Quality' to avoid too much buffering and to enjoy a smooth experience. Later on, the recordings are great for rehearsal and repetition of the material. You can download and watch them offline while commuting, or what I'm going to do in the future - to use them as material for a study group within the Mauritius Software Craftsmanship Community (MSCC). For sure, this is going to be a lot of fun, and I'm looking forward to work with other Windows-oriented software craftsmen in order to 'push' them towards Microsoft certifications. By chance, I discovered today that my quote has been published in the MVA Voices section: Click to enlarge: Screenshot of Microsoft Virtual Academy web site taken on 04.07.2013 Thank you very much, MVA - this made my day and I'm very happy to be quoted. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'quoted-on-mva-voices.html'; this.page.identifier = '9271ed1c97_idquoted-on-mva-voices'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "randale-ein-normaler-samstag.html": {
    "href": "randale-ein-normaler-samstag.html",
    "title": "Randale? - Ein normaler Samstag - Get Blogged by JoKi",
    "summary": "Randale? - Ein normaler Samstag Copy Markdown View Markdown GitHub Markdown Source Randale? - Ein normaler Samstag Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'randale-ein-normaler-samstag'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'randale-ein-normaler-samstag'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Randale? - Ein normaler Samstag') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-23 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Die Vorbereitungen für Sperrmüll - Entsorgung von überfälligen Ablagerungen in Haus, Keller und Garten - waren recht kurzfristig und spontan angesetzt. Die eigentliche Kernaufgabe hiess: Schwimmbecken im Garten abschlagen, zerkleinern und an der Straße für den Sperrmüll lagern. Okidoki... Und so ging es mit der tatkräftigen Unterstützung von Golo und Thomas gegen 11:00 heute Morgen ans Werk. Der Rost hatte bereits einiges an Vorarbeit am Becken geleistet, so dass die Hauptaufgabe lediglich darin bestand, sich nicht zu verletzen. Was auch wieder spannend bei Sperrmüll ist, sind die Schrottsammler und Schnäppchenjäger. Kaum hatten wir die ersten Teile an der Straße abgelegt, hielt auch bereits ein Metallsammler an, und unterstützte uns tatkräftig bei den Ausmistungen. Richtig cool. Hm, was und wie die Jungs wohl verdienen? Leider haben wir dieses Mal auch meine 1.5 Meter Parabolschüssel für den SAT-Empfang weggegeben. snief - Ein gutes Stück und ich hatte schon die Anschlussleitungen und den PC für Abhorchaktionen vorbereitet. Naja, Schwamm drüber, ich habe für sowas eh zu wenig Zeit. Nach der Metallaktion ging's weiter in der Holzabteilung - Zusammensammeln, zerkleinern und deponieren im Holzschuppen; der nächste Winter kommt bestimmt. Und irgendwie haben andere Mitmenschen Probleme beim Laufen in unwegsamen Gelände, aber darüber werde ich mich nicht weiter auslassen... Golo weiß Bescheid. Fein, fein, damit wäre der Bereich Garten und ein Teil der Garage bereinigt. Apropos Garage, dort haben wir dann weiter gemacht und einiges an Plastik - windzerfetzte Seitenwände von Gartenpavillons und solch' netten Dinge - zusammengetragen und in Müllsäcken verstaut. Und durch die Garage ging's dann weiter in den Keller. Glücklicherweise gab's dort nicht allzuviel zu entsorgen. So, fertig? Neee, Pustekuchen, die leerstehende Anliegerwohnung ist noch von älteren Möbelstücken zu befreien - Apropos, falls jemand Interesse an einer 1.5ZKBGarten Anliegerwohnung mit separatem Eingang und Stellplatz hat, bitte bei mir melden. Die alte Küche muss einer neueren Datums weichen, und so zerlegten wir das ein oder andere Stück, bevor es hoch an die Straße ging. Im Laufe des Nachmittags - geschätzte 15:00 hatten wir den Großteil entsorgt. Feierabend. Und diesen gilt es mit Entspannung zu geniessen. Und was bietet denn schon mehr Entspannung als ein paar gepflegte Racing-Runden mit Mario Kart: Double Dash!!? Ganz einfach die Aussicht auf Gegrilltes! Yeah, wir hatten zwar schon einige Feuerstellen und Grillgestellteile beim Aufräumen in Garage und Keller entdeckt, aber irgendwie war da kein vollständiger dabei. Ich habe keine Ahnung, was meine Geschwister mit den Grill anstellen... Aber zum Glück stand auf der Terrasse noch ein vollständiges Exemplar. Holzkohle, Anzünder und Feuerzeug... tjoa, Feuer - reden wir besser nicht drüber Let's brutzzel... Nachdem endlich die Grundversorgung mit Feuer bereitgestellt wurde, fing's an zu regnen... Boah, nix da! Es wird gegrillt, wenn gegrillt werden soll, und von ein paar Tropfen Wasser habe ich mich noch nie abschrecken lassen. Oder bist du etwa aus Zucker? - Na also, und so gibt's dann auch 'ne gescheite Glut, saftige Steaks, knusprige Bauchstücke und triefende Käsegriller garniert mit Salaten als Abschluss des Tages. Wenn da nicht diverse Hektikmacher wären... Aber nee, lassen wir das. Es hat sich später als angehenden 'Running Gag' herauskristallisiert, während Thomas und ich in einen gemütlichen DVD-Abend übergingen. Eines möchte ich noch schreiben: DANKE an Thomas und Golo für die tatkräftige Unterstützung beim Sperrmüll entsorgen und sowie beim Grillen. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'randale-ein-normaler-samstag.html'; this.page.identifier = '9271ed1c97_idrandale-ein-normaler-samstag'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "re-blogged.html": {
    "href": "re-blogged.html",
    "title": "Re-Blogged - Get Blogged by JoKi",
    "summary": "Re-Blogged Copy Markdown View Markdown GitHub Markdown Source Re-Blogged Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 're-blogged'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 're-blogged'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Re-Blogged') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-03-19 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Another sleepless evening or better said sleepless night gave me some time to work on the visual presentation of this blog. During the last weekend I was looking for some ideas on web site templates. Well, there are a lot of free (as in beer) resources out there but nearly everything did not match my favorite taste. After quite a long time searching I came across a web site with a great collection of CSS templates.It is really interesting to see what other people especially designers are capable of. But honestly I prefer to have my focus on software design and development. The original CSS template used for this blog is called 'Spheroids'. Because of the implementation of the blog software and my personal expectations I tweaked the CSS definitions. The result is what you see now... \uD83E\uDD2A And just for the records I made a screenshot of the previous design. This is not only for fun but gives a good overview how this blog was enhanced over a certain time period. Hm, if I remember correctly, than this change in design is now the fifth one. Maybe I find some older screenshots... could be very interesting.Finally, I'd like to say that these sleepless nights are not funny. Anyways and sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 're-blogged.html'; this.page.identifier = '9271ed1c97_idre-blogged'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "regular-expressions-in-vfp.html": {
    "href": "regular-expressions-in-vfp.html",
    "title": "Regular Expressions in VFP - Get Blogged by JoKi",
    "summary": "Regular Expressions in VFP Copy Markdown View Markdown GitHub Markdown Source Regular Expressions in VFP Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'regular-expressions-in-vfp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'regular-expressions-in-vfp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Regular Expressions in VFP') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-04-22 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); **Motivation ** Based on some research I did in december 2005 and currently refreshed due to a vital discussion in on of Microsoft's news groups at news://msnews.microsoft.com/microsoft.public.de.fox (German) about the future of Visual FoxPro, I decided to finalize and deploy my COM Proxy for .NET. As you might guess the name of this component is its main purpose. It is a dynamic-link library that provides features of the .NET framework classes via COM to any legacy programming language like Visual FoxPro. Contrary to current Sedna CTP this component has the same interface for Regular Expressions as the VBScript object has. So, this way it is a lot easier to switch between different implementations. Setup and Installation The component comes in two flavors - for .NET Framework 1.1 and 2.0. The setup routine installs both in separate folders and registers both in Windows. But no fear, the ProgID is the same for both, so it's again very handy for a VFP developer. Last registration of the component is the active one. I'd like to mention that the registration uses the /codebase switch of regasm tool and therefore creates no entries in to the Global Assembly Cache (GAC) on the machine. The current version of the setup is available at my German blog at the URL https://jochen.kirstaetter.name/files/ComProxyForNet.msi Just get the file and follow the installation instructions. Usage in Visual FoxPro So, while the components are COM-based dynamic-link libraries usage in Visual FoxPro is done with its CreateObject() function. The returned object reference is an instance of .NET's Regex class. RegEx = CreateObject(\"ProLib.RegEx\") ? RegEx.IsMatch(\"abc\",\"w\") You can use any option and functionality of System.Text.RegularExpressions.Regex in Visual FoxPro now. Further information and details on this namespace are provided in the MSDN Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'regular-expressions-in-vfp.html'; this.page.identifier = '9271ed1c97_idregular-expressions-in-vfp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "representing-mauritius-in-the-2013-bench-games.html": {
    "href": "representing-mauritius-in-the-2013-bench-games.html",
    "title": "Representing Mauritius in the 2013 Bench Games - Get Blogged by JoKi",
    "summary": "Representing Mauritius in the 2013 Bench Games Copy Markdown View Markdown GitHub Markdown Source Representing Mauritius in the 2013 Bench Games Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'representing-mauritius-in-the-2013-bench-games'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'representing-mauritius-in-the-2013-bench-games'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Representing Mauritius in the 2013 Bench Games') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-10-29 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Only by chance I came across an interesting option for professionals and enthusiasts in IT, and quite honestly I can't even remember where I caught attention of Brainbench and their 2013 Bench Games event. But having access to 600+ free exams in a friendly international intellectual competition doesn't happen to be available every day. So, it was actually a no-brainer to sign up and browse through the various categories. Most interestingly, Brainbench is not only IT-related. They offer a vast variety of fields in their Test Center, like Languages and Communication, Office Skills, Management, Aptitude, etc., and it can be a little bit messy about how things are organised. Anyway, while browsing through their test offers I added a couple of exams to 'My Plan' which I would give a shot afterwards. Self-assessments Actually, I took the tests based on two major aspects: 'Fun Factor' and 'How good would I be in general'... Usually, you have to pay for any kind of exams and given this unique chance by Brainbench to simply train this kind of tests was already worth the time. Frankly speaking, the tests are very close to the ones you would be asked to do at Prometric or Pearson Vue, ie. Microsoft exams, etc. Go through a set of multiple choice questions in a given time frame. Most of the tests I did during the Bench Games were based on 40 questions, each with a maximum of 3 minutes to answer. Ergo, one test in maximum 2 hours - that sounds feasible, doesn't it? The Measure of Achievement While the 2013 Bench Games are considered a worldwide friendly competition of knowledge I was really eager to get other Mauritians attracted. Using various social media networks and community activities it all looked quite well at the beginning. Mauritius was listed on rank #19 of Most Certified Citizens and rank #10 of Most Master Level Certified Nation - not bad, not bad... Until... the next update of the Bench Games Leaderboard. The downwards trend seemed to be unstoppable and I couldn't understand why my results didn't show up on the Individual Leader Board. First of all, I passed exams that were not even listed and second, I had better results on some exams listed. After some further information from the organiser it turned out that my test transcript wasn't available to the public. Only then results are considered and counted in the competition. During that time, I actually managed to hold 3 test results on the Individuals... Other participants were merciless, eh, more successful than me, produced better test results than I did. But still I managed to stay on the final score board: An 'exotic' combination of exam, test result, country and person itself Representing Mauritius and the Visual FoxPro community in that fun event. And although I mainly develop in Visual FoxPro 9.0 SP2 and C# using .NET Framework from 2.0 to 4.5 since a couple of years I still managed to pass on Master Level. Hm, actually my Microsoft Certified Programmer (MCP) exams are dated back in June 2004 - more than 9 years ago... Look who got lucky... As described above I did a couple of exams as time allowed and without any preparations, but still I received the following mail notification: *\"*Thank you for recently participating in our Bench Games event. We wanted to inform you that you obtained a top score on our test(s) during this event, and as a result, will receive a free annual Brainbench subscription. Your annual subscription will give you access to all our tests just like Bench Games, but for an entire year plus additional benefits!\" -- Leader Board Notification from Brainbench Even fun activities get rewarded sometimes. Thanks to @Brainbench_com for the free annual subscription based on my passed 2013 Bench Games Master Level exam. It would be interesting to know about the total figures, especially to see how many citizens of Mauritius took part in this year's Bench Games. Anyway, I'm looking forward to be able to participate in other challenges like this in the future. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'representing-mauritius-in-the-2013-bench-games.html'; this.page.identifier = '9271ed1c97_idrepresenting-mauritius-in-the-2013-bench-games'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "request-timed-out.html": {
    "href": "request-timed-out.html",
    "title": "Request timed out - Get Blogged by JoKi",
    "summary": "Request timed out Copy Markdown View Markdown GitHub Markdown Source Request timed out Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'request-timed-out'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'request-timed-out'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Request timed out') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2004-07-13 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Es ist schon faszinierend aber auch ebenso nervig zu beobachten, daß auch das Netz der Netze etliche Tücken und Fallen zu bieten hat. Naja, mal fix 'ne Mail an die DNS-Verwalter rausgejagt - mal sehen, was da als Response zu erwarten ist. Vllt. sind es ja nur irgendwelche nächtlichen Wartungsarbeiten, wer weiß... Aber wenn man während des 'gestörten' Zeitraums selbst ein paar Wartungsarbeiten oder Erweiterungen in den eigenen Sites integriert, dann ist es mehr als nervig, wenn andauernd - bisweilen sogar im 5 Minuten-Rhythmus - die Connection abreißt, egal welches Protokoll man sich auch zurecht gelegt hat... Nunja, es wird vorübergehen und bessere Zeiten kommen. Einen Vorteil hat es ja dennoch, man beschäftigt sich mal wieder mit den Distanzen zu seinen Servern und hierzu kann ich lediglich sagen, daß ich mit einer Antwortzeit von 10 bis 20 Millisekunden, sowie konstanten 6 Hops eine sehr direkte Verbindung habe. Zumindest, was unsere Server in Deutschland anbelangt... Die amerkanischen Server reagieren mit durchschnittlichen 100ms meines Erachtens ebenfalls extrem gut. Ob's nun an FastPath liegt... könnte sein, oder? Naja, schauen wir einfach mal wann sich die Sachlage beruhigen wird. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'request-timed-out.html'; this.page.identifier = '9271ed1c97_idrequest-timed-out'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "resume-on-30-days-of-sharepoint.html": {
    "href": "resume-on-30-days-of-sharepoint.html",
    "title": "Resume on 30 Days of SharePoint - Get Blogged by JoKi",
    "summary": "Resume on 30 Days of SharePoint Copy Markdown View Markdown GitHub Markdown Source Resume on 30 Days of SharePoint Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'resume-on-30-days-of-sharepoint'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'resume-on-30-days-of-sharepoint'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Resume on 30 Days of SharePoint') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-09-30 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Dear readers, as you might have noticed... It was an organisational desaster on my end! Even though I continued my studies and research on Microsoft SharePoint 2013 during the last 30 days, I wasn't able to write an article a day to keep you posted on my progress. Nonetheless, I gathered a good number of additional blogs, mainly SharePoint MVP sites, and online forums which will be helpful in the next couple of weeks while I'm actually going to develop a C#-based client which will enable an existing 'legacy' application to SharePoint as a document management system (DMS) besides other already existing solutions. Finding excuses Well, no. Not really. I simply didn't block any or enough time every day to write down my progress during my own challenge. My log book on learning about SharePoint stands at 41 hours and 15 minutes during this month. Which means that I spent an average of more than 1 hour per day on getting into SharePoint. I know that might sound a little bit low but also keep in mind that I went for the challenge on top of my daily job and private responsibilities. During the same period there had been two priority 0 incidents from clients - external root cause - which took presedence over this leisure project. More to come Anyway, it was a first trial and despite the low level of reporting on my blog, I'm confident about what I learned during the last 30 days, and I'm ready to implement the client's requirements. At least, I would say that I have a better understanding about the road map or the path to walk during the next month. As time and secrecy allows I'm going to note down some bits and pieces... During the process of development, I'm going to 'cheat' on the challenge summary article and add links to those new entries. Just for the sake of completeness. Next challenge? Hmm, there had been ideas during the last meetup of the Mauritius Software Craftsmanship Community (MSCC) regarding certifications in IT and eventually we might organise some kind of a study group for specific exams, most probably Microsoft exams towards MCSD Web Developer or Windows Developer. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'resume-on-30-days-of-sharepoint.html'; this.page.identifier = '9271ed1c97_idresume-on-30-days-of-sharepoint'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "retrospective-2018.html": {
    "href": "retrospective-2018.html",
    "title": "2018: A retrospective - Get Blogged by JoKi",
    "summary": "2018: A retrospective Copy Markdown View Markdown GitHub Markdown Source 2018: A retrospective Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'retrospective-2018'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'retrospective-2018'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : '2018: A retrospective') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-01-03 - Filed under Personal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Although every year has 365.24 days each has its own, subjective performance. 2018 largely felt like it had been a fantastic ride for me right from the beginning. I'm going to break down my experience by month, and I hope it offers one or two interesting topics for you, too. January - Cyclones & Super Blue Blood Moon Mauritius is prone to annual cyclone activities. This comes naturally for a tropical island and the year started directly with Berguitta. What a beauty she was: For reference, the distance between Port Louis and Saint-Denis is approximately 230 km (or 143 ml). Our local meteo service does not classify cyclones based on strength or intensity, we have warning classes only. Luckily, she passed by nicely and provided lots of rain only. Later this month, we could experience a Super Blue Blood Moon, an event of a lifetime. We watched several locations live on TV. The following article tubbed Super Blue Blood Moon 2018: Here Are The Best Photos and Videos has lots of explanation and linked material on the event. February - Study Jam & Azure VM for the kids In February I went forward to organise a Study Jam regarding Google's Mobile Sites Certification. Although I was hoping to reach out to more community members it actually turned out to be the right number of attendees that came to the Study Jam. All learning material is provided by the Google Academy for Ads for free and passing the assessment can be done at the convenience of your own computer at any time. This certification is valid for one year as there is a constant flow of changes happening. I passed that certification already twice. At home I was surprised by a little challenge coming from our children. They both have computer courses - ICT skills at primary school - at their school which covers basic use of applications from the Microsoft Office suite. As their laptop is running on Xubuntu Linux with LibreOffice I set up a clean Windows 10 VM on Azure, and gave them a desktop icon to remote connect into that machine. Problem solved... March - 100 Days of Exam & SQL Server on Linux After quite some time of active procrastination I thought that it was finally the right moment to get my stuff together and opt-in for industry exams. The main impulse came from my intention to go for the MCSA: Linux on Azure certification - which would allow me to literally combine both of my favourite activities in IT: Cloud computing (using Microsoft Azure) and Linux. To keep myself accountable and focused on the challenge to learn and sit for the upcoming exams I drew inspiration from Alexander Kallaway's 100 Days of Code to create a side-project on my own: #100DaysOfExam The project is hosted on GitHub and the hashtag #100DaysOfExam is used on Twitter to report daily progress. Would be great to see more aspirants using this approach to prepare for an exam. How about you? At the end of March we had a fantastic meeting with great sessions on Linux (especially on SSHd), SQL Server on Linux and SQL Client tools on Linux. This joint meeting was organised by delegates of the Linux User Group of Meta (LUGM), Professional Association for SQL Server (PASS) Mauritius and MSCC and had over 60 attendees, mainly students of SUPINFO. April - Out with the old, in with the new My then current daily working machine was about to fade away. You know this kind of little issues that happen after a few years to laptops, i.e. battery broken, glitches on the keyboard, etc. When the hinges of the top lid started to cause trouble on the display it was overdue to purchase a new machine. Luckily, a friend had already planned their vacation on Mauritius and they offered some space and weight allowance in their luggage. Like the previous laptop I purchased a recent MSI laptop with decent RAM, disk storage and NVidia GeForce GTX 1060 graphics that would allow me to hook up a VR headset at a later stage. Also in April we, read: MSCC, participated in Global Azure Bootcamp (GAB) to spread the message about Azure being \"a cloud for everyone on every device\". It was good fun and we were able to hand out Azure passes with some credits to all attendees. Last year, I met Magnus Mårtensson - one of the organisers of GAB - at the annual C# Corner Conference and we had quite an exhaustive exchange about technology, politics, cultural differences & diversity, communities, and conference activities. Back then, we started to talk about an epic idea... Stay tuned for 2019. May - Developers Conference May was fully focused on the IT event of the year here in Mauritius, at least for me and a lot of other software craftsmen: Developers Conference The event had been covered in the local newspaper DevCon 2018: aux développeurs de jouer ce jeudi and this year we had Manishka as a dedicated social media promoter who did an outstanding job taking pictures and shooting live streams for Facebook from almost all sessions. More details, photos and everything else are covered by a dedicated article on Developers Conference 2018. It was awesome! June - FIFA World Cup Yup, we did it. Actually, we did extreme FIFA World Cup as I managed to watch every soccer match live this year. Additionally, the children were busy to collect stickers for their Panini album. We also went to a few sticker exchange gatherings. Where the kids even got interviewed and broadcasted on l'express - Mondial 2018: au cœur de la panini mania. Les Kirstätter talking about their interest in the World Cup 2018 Their \"performance\" starts at the 1:42 minute marker. July - Microsoft MVP Award Woohoo, the most anticipated email showed up in my inbox - I have been re-awarded with the Microsoft Most Valuable Professional (MVP) award. This year marked my five years of being an MVP awardee - 2006, 2007, 2016, 2017 and now 2018. I'm really humbled by this recognition, and it motivates me to put more effort into my IT passion. While I replaced my daily development machine I also used the opportunity to sent my previous laptop to refurbishment - general cleaning, replacement of broken parts, etc. -, and then I picked up the latest Windows 10 Insider version and built the system from scratch. The hardware specs of that laptop are still top-notch and the built-in NVidia GTX 870M has enough juice to run a DIY Windows Game Box (Steam, Itch, Xbox, Store, Scumm, etc.). Note: Give it shot: \"Return of the Tentacle - Prologue\" is a fan project and the unofficial sequel to the iconic adventure game \"Day of the Tentacle\". Purple Tentacle is back and tries to conquer the world and enslave humanity once more. The three friends Bernard, Laverne and Hoagie make their way back to the mansion of the mad scientist Dr. Fred – time travel should help saving the world. August - To make or not to make? Another strong month of community activities and collaboration between user groups. We teamed up some speakers form the Front-End Coders and MSCC to speak about various topics. First time that we visited the Digital Factory by MCB in Port Louis. You can read the full article about our Code Saturday for more details and photos. A new era has been launched by the Mauritius Maker Community (MMC). With the opening of their MakerSpace there are now opportunities for makers of any age to meet, to exchange about their projects and to learn about electronic circuits. Especially the young makers... Talking about \"making\"... We got confirmation that our initiated \"Project Jamba\" is up well and scheduled for first half of 2019. I'm absolutely thrilled about our upcoming family member. September - Google Developer Groups Surely, the highlight of the month, probably the year 2018, was the trip to Nairobi, Kenya to attend the annual, regional SSA Community Summit 2018 and DevFest Nairobi with over 1,200 attendees. Having had this opportunity to meet and exchange with other GDG leads from the Sub-Saharan Africa (SSA) region was very enriching experience and it helped me to shift my view on a few things. There are too many names to list here, and it wouldn't give each single one enough credit either. It also encouraged me to foster my community activities more towards Google technology, and to look out for sustainable ideas that could be realised with modern technology. But... (isn't there always some kind of \"but\"?) September also brought us our monthly MSCC meeting where I spoke briefly about the state of .NET Core 2.1 (with its long-term support), the roadmap to .NET Core 2.2 and 3, as well as the concepts behind Blazor. Speaking of .NET Conf, one of my tweets asking about how to join the .NET Foundation had been picked up and answered live by Scott Hanselman, Community Director for the .NET Team and Jon Galloway, Executive Director of the .NET Foundation during their session. Local chapter of .NET Conf in Mauritius - Check the world map... Eventually it might look weird to someone else combining both Google and Microsoft technology stacks. For me it's absolutely the opposite and knowing about both worlds and the Linux universe provides me with a richer experience than a single entity could possibly offer. October - Family affairs & Hacktoberfest As every year around this time we have family members on vacation here. Given our daughter's interest in the Catholic faith it happened that we all came together to celebrate her \"trinity of events\" in one day. The sermon was beautifully arranged and I had to do my part, too. I was cited to the podium (NR: bema or ambol) to read a passage in German language. Standing up there in front of the community reciting was quite some fun actually. In regards to software community all flags were up for Hacktoberfest. And our MSCC members Humeira and Pritvi stepped up to get everything organised for the monthly meeting. Did anyone actually receive their T-shirt already? Oh, not to forget. The fruits of #100DaysOfExam started to bring in the harvest. Back in August, I sat for three Microsoft beta exams and I passed them all. Meaning, I'm now a Microsoft Certified: Azure Administrator Associate. Stupid me, actually twice... \uD83D\uDE09 November - Relaxing & more beta exams As part of my family stayed for almost four weeks with us, we decided to use the time and go on a small road trip down South of Mauritius. On the southern shores of Mauritius are no protective reefs and the sea shows its majesty on the volcanic rock formations. Connecting with nature is pure and highly recommended to relax. Later on, I sat for two more Microsoft beta exams. Usually I would go onto the website of Pearson VUE to choose a test centre and fix an appointment for the exam. Unexpectedly, for those two exams everything turned out to be new. Why? Because none of the local registered test centres would offer those exams, and therefore I had to choose the only available option, the Online Proctored Exam. The procedure is well-documented online, so I thought, but there are some nifty details to pay attention to. I am going to spare you with all details here but will write an article about my experience doing those two online proctored exams. Pinky promise! Note: I'm still waiting for the results of both beta exams. There had been some re-designing at Microsoft Learning regarding the exam content. Crossing fingers! December - DevFest Mauritius & Happy Holidays The month of December started with a Bang! What an amazing event DevFest Mauritius 2018 was. As part of the GDG Mauritius we managed to bring over 100 attendees and 15 speakers together and to have conversations about the latest technology stacks and trends offered through Google. DevFest was well planned and executed thanks to lots of helpers involved in the process. Although I was content with the number of Microsoft exams already taken, I couldn't ignore an unexpected announcement of another beta exam by Microsoft. Hence I seized the opportunity and scheduled my 6th appointment of 2018 at a local test centre. More about that adventure in a future article. Final thoughts and outlook on 2019 Each time I am doing something that I love to do or that I'm passionate about I get the subjective impression that the spacetime continuum has a different beat and the days pass by faster than usual. Reflecting on some of the activities done throughout the year I have to admit that 2018 was an amazing one. With the next junior hacker joining us soon I am going to shift my focus on my family. All those previous months I received strong support from my lovely wife, which during 2019 will be my turn to return the favour. This being said, I am also looking forward to pass on a number of administrative tasks to other members of the MSCC. I have no doubt that this will help to grow the user group, and I'm sure that a few craftsmen are ready and up for the challenge. Happy New Year 2019! PS: New exam appointments are already in the making. Image credit: Sebastien Pointu if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'retrospective-2018.html'; this.page.identifier = '9271ed1c97_idretrospective-2018'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "revamped-google-webmaster-tools.html": {
    "href": "revamped-google-webmaster-tools.html",
    "title": "Revamped Google Webmaster Tools - Get Blogged by JoKi",
    "summary": "Revamped Google Webmaster Tools Copy Markdown View Markdown GitHub Markdown Source Revamped Google Webmaster Tools Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'revamped-google-webmaster-tools'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'revamped-google-webmaster-tools'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Revamped Google Webmaster Tools') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-04-15 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); With a positive surprise I realized today that Google's Webmaster Tools had some minor overhauling and provide some more details than before. Most obvious are the changes on the dashboard where the Top Search Queries now provide information about impressions and clicktroughs instead of the rankings before. Only the links of the search expressions are missing. It seems that the Top search queries were in the focus of this update. The section is now spiced with detailed graphs about what happened during selectable periods on your site. Well, seems that the Webmaster Tools mimic a stripped-down version of Google Analytics... I was very pleased by the details that are offered when you click on a single query term. Really nice to see the search rankings and your responsible URLs at the same time. Before, you had to put two browser instances side-by-side to achieve this kind of overview. Personally, I like the approach to visualize statistics the way Google or other providers do. It gives you a quick and informative overview, and enables you to dig further into details about peaks and lows on your visits, page impressions or clickthroughs. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'revamped-google-webmaster-tools.html'; this.page.identifier = '9271ed1c97_idrevamped-google-webmaster-tools'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "rock-im-brauhof-2005.html": {
    "href": "rock-im-brauhof-2005.html",
    "title": "Rock im Brauhof 2005 - Get Blogged by JoKi",
    "summary": "Rock im Brauhof 2005 Copy Markdown View Markdown GitHub Markdown Source Rock im Brauhof 2005 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'rock-im-brauhof-2005'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'rock-im-brauhof-2005'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Rock im Brauhof 2005') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-27 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ich liebe 'meine' Brauerei... *8 Stunden! 8 Bands - Das \"Bischoff-Pfalz-Open Air \"05\" in Winnweiler Das Konzertereignis des Sommers: Bischoff Pfalz Open Air mit SWR3 Winnweiler. Am Samstag, dem 27. August 2005, rockt der Bischoff-Brauhof beim größten regionalen Musikfestival dieses Sommers. Nachdem der erstmalig in 2004 veranstaltete \"Rock im Brauhof\" ein riesiger Erfolg war und trotz starken Regens über 7.000 Zuschauer anzog, freut sich die Brauerei, auch in diesem Jahr tausende von Musikfans auf ihrem Gelände zu begrüßen. Ab 15:00 Uhr bietet Bischoff zusammen mit den Partnern SWR3 und EigenARTevents 8 Stunden Livemusik der Spitzenklasse bei freiem Eintritt (Einlass ist ab 14:00 Uhr). Hauptact ist erneut die Kirchheimbolander Kultband Snailshouse, die schon letztes Jahr die Zuhörer mit gehaltvollem Deutschrock begeistert hatte. Aufgrund der besonderen Beziehung zwischen Snailshouse und der Bischoff-Brauerei, die die junge Band seit Beginn ihrer Musikkarriere tatkräftig unterstützt hat, nehmen die vier Musiker aus der Pfalz gerne die Gelegenheit wahr, sich bei Bischoff und allen pfälzischen Fans mit einem heimischen Konzert zu bedanken. Mit von der Partie werden sein: \"Mannheimer Sohn\" Rolf Stahlhofen & Band, die pfälzische Partyband Greencard, die Amerikanerin Stefanie D., die junge Pop-Gruppe Freistil aus Stuttgart, die Alternativ-Rocker Perblind aus Karlsruhe, die am Donnersberg heimische Coverband swob, sowie Normann Böttchers aus der SAT1 Serie StarSearch. Diese Top-Acts werden den Brauhof durch fetzige Musik zum Beben bringen. Durchs Programm führen die beiden bekannten SWR3-Moderatoren Michael Leupold und Ben Streubel und beschließen den Tag bei Bischoff mit der SWR3 Dancenight.* Ich hab' mir zwar nicht die vollen 8 Stunden Musik reingezogen, aber doch einige und es hat sich rentiert! Also, bisher haben die Partyaktionen bei der Privatbrauerei Bischoff immer Spass gemacht, egal ob's Tag der offenen Tür, Rock im Brauhof oder irgendeine andere Party ist... Leute, da schäumt das Bier! Es ist schon faszinierend, was die Jungs bei der Brauerei auf die Beine stellen. Getreu dem Motto 'Think global - drink local' wird die Sache angegangen und es macht richtig Spass. Ich hoffe, wir sehen uns beim nächsten Event! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'rock-im-brauhof-2005.html'; this.page.identifier = '9271ed1c97_idrock-im-brauhof-2005'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "rock-your-code-defensive-programming-for-microsoft-net.html": {
    "href": "rock-your-code-defensive-programming-for-microsoft-net.html",
    "title": "Rock Your Code: Defensive Programming for Microsoft .NET by David McCarter - Get Blogged by JoKi",
    "summary": "Rock Your Code: Defensive Programming for Microsoft .NET by David McCarter Copy Markdown View Markdown GitHub Markdown Source Rock Your Code: Defensive Programming for Microsoft .NET by David McCarter Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'rock-your-code-defensive-programming-for-microsoft-net'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'rock-your-code-defensive-programming-for-microsoft-net'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Rock Your Code: Defensive Programming for Microsoft .NET by David McCarter') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-02-03 - Filed under RecensionDevelopment (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Talking about Defensive Programming during times of Test Driven Development, Clean Code, Domain Driven Development and all other kinds of buzzwords seems a bit unusual. From his long-year experience David McCarter shares a collection of rules of thumb to write better, rock-solid code. The book is part of his conference series titled Improving Code Quality... One Developer at A Time. Back in 2017 I met David at the C# Corner conference in Delhi, India. While I gave a session on .NET Core on Linux, he had several sessions on how to be successful as a software developer and how to improve programming skills by sharing his experience as a programmer for over two decades. His series Improving Code Quality... One Developer at A Time was already in full swing and the review on his book on Defensive Programming is a milestone on this journey. The targeted audience of the title are .NET developers with little to a few years of experience. The major focus is on how to write code that avoids causing exceptions. And in case that an exception happens on how to deal with it depending on the application layer it occurs. Lastly, David writes about techniques to log exceptions in .NET Core. Stop exceptions before they happen Of course, the best way to deal with an exception is not to cause an exception. And that's how defensive programming adds value to your code base. Use the existing features of .NET and .NET Core to check the state of objects, properties and parameters before using them further. Typically, one would always check an object or property for null. If you're working with file system operations check whether a file really exists before accessing it. Treat all incoming parameters and data as incomplete or damaging to your program execution. If the state of an object or property does not match the expected situation stop any further execution and return back to the caller. Stay on the positive path... Lastly, David gives a few tips on how to deal with exceptions that are still occurring inclusive the ones that our code might actually throw given incorrect state evaluation. The .NET Framework has several options on where to catch such exception, inclusive the option to implement an \"catch-all\" exception handler that wouldn't miss a single beat. The book has 42 pages in total and is relatively short in regards to the topic overall. Given the length of Rock Your Code - Defensive Programming it is a good fit for a couple of lunch breaks, one or two commute units or a dedicated Friday afternoon learning session. Some information might be too obvious to an experienced .NET developer but surely a good resource for junior developers to get on the right track of writing better source code from the very beginning. You can get a copy of Rock Your Code - Defensive Programming on Amazon at an exceptional low price. This book brings writing better code to the next level. The bespoken book is one of the newer titles of David McCarter; available on Amazon. You might also be interested in his advice on how to Rock Your Technical Interview. Disclaimer: I'm a technical editor of the book. Image credit: John Pratt if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'rock-your-code-defensive-programming-for-microsoft-net.html'; this.page.identifier = '9271ed1c97_idrock-your-code-defensive-programming-for-microsoft-net'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "router-in-mauritius.html": {
    "href": "router-in-mauritius.html",
    "title": "Inspecting internet router in Mauritius - Get Blogged by JoKi",
    "summary": "Inspecting internet router in Mauritius Copy Markdown View Markdown GitHub Markdown Source Inspecting internet router in Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'router-in-mauritius'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'router-in-mauritius'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Inspecting internet router in Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2017-10-18 - Filed under GeneralPersonal (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Inspired by a posting on Facebook and a developing late night conversation with Irshaad Abdool on Monday I got some motivation to have a closer look myself. Irshaad mentioned in his article on Vulnerabilities on MT FTTH Routers that the local IT community hackers.mu organised their first vidcast, or better said Hangouts On Air, talking about same issue actually. Please find the recording on YouTube: The hangout session explains and shows several security concerns related to the FTTH modem/router/gateway provided by Mauritius Telecom. This is related to an article Behind the Masq: Yet more DNS, and DHCP, vulnerabilities on the Google Security Blog. The main issue is due to a popular software used in such devices: Dnsmasq. Although, I find this report already serious I was a bit more concerned about the default settings, enabled features, and login credentials of same router. So, let's get a bit more into the details. Huawei HG8245H provided by Mauritius Telecom After successful installation of a fiber line at your premises you'd be given access to the new modem. The default password to access the WiFi network might be written on the box already, and you might be told that the login credentials for you are default. Just in case, it's root : admin - and as tech-savvy person you feel like master of the fiber connection at your place. In my case, I started to explore the web interface to immediately change the account password, then to review all the current settings, and to make minor adjustments like SSID and password of the wireless network, etc. The first speed bump occurred when I wouldn't be able to change the DNS server information in the DHCP configuration for all the attached client devices on the network. That's kind of weird. Since early days of internet usage I prefer to rely on public DNS infrastructure like either Google DNS or OpenDNS rather than being stuck with the assigned DNS servers of the internet service provider (ISP). Having had bad experience already back in Germany, and after several interesting DNS mismatches produced by local ISPs, I prefer to use reliable and trusted resources. Additionally, given that I have young internauts in our household I appreciate the extra layer of security and safety that services like OpenDNS FamilyShield offer. You might like to read more about Introducing FamilyShield Parental Controls on the Cisco Umbrella Blog. This already happened some weeks back and other modem owner reported that they would be able to change the DNS setting without any obstacles. Well, something is different here, maybe I'm doing it wrong... Surprise... Second account: Super Administrator During the conversation with Irshaad and based on other indications where people mentioned certain settings that are not available in my web interface of the modem, it became obvious that I'm definitely doing something wrong. The router actually has a second, more powerful user account enabled by default. Yup, that one isn't communicated to mere mortals like the regular consumer. That additional account gives you an extended web interface to manage your device. Depending on the preferences of your ISP this might vary but here in Mauritius the credentials are telecomadmin : admintelecom Eventually, you have to search the default credentials on the web in case those ones don't work for you. Now, it would be possible to change DNS settings and much much more. The missing settings magically appeared on the web interface, and so forth. It's like an advanced mode of modem configuration. The administrator uses the initial password. If you want to change this password, please contact the telecom carrier. For details about how to change the password, see the Security Maintenance from http://support.huawei.com. Well, there is just one major problem here: That password cannot be changed! Meaning, every customer of Mauritius Telecom that has this modem is exposed. Good bye, internet access for friends and family. Anyone connected to your network would be able to log into your device. The web is not enough: Telnet Referring back to the Hangout by hackers.mu there are other default settings and features sleeping in your modem. For example, apart from the familiar web interface to manage your router there is another access via Telnet enabled by default. Telnet itself is an unencrypted protocol to connect to remote systems and manage them. Telnet is from the early days of internet usage when security concerns were not that obvious or present. Log in with the super user account and navigate to Security > ONT Access Control Configuration to review and uncheck any Telnet access options, as shown in the screenshot above. If you logged in using the regular root account you might see the following instead. Afterwards, you should verify that Telnet service has been disabled using a Command Prompt, PowerShell or Terminal, like so: $ telnet 192.168.100.1 Trying 192.168.100.1... telnet: Unable to connect to remote host: Resource temporarily unavailable Are there more services running? Despite disabling Telnet on the modem it might also be interesting to check whether there are other ports accessible on the device. Well, turns out there are... PORT STATE SERVICE VERSION 21/tcp filtered ftp 22/tcp filtered ssh 23/tcp filtered telnet 53/tcp open domain dnsmasq 2.49 80/tcp open ssl/http? 49152/tcp open upnp Portable SDK for UPnP 1.6.18 (Linux 2.6.34.10_sd5115v100_wr4.3; UPnP 1.0) 49153/tcp open upnp Portable SDK for UPnP 1.6.18 (Linux 2.6.34.10_sd5115v100_wr4.3; UPnP 1.0) Those filtered entries are the UI-disabled settings, as you might have seen on the screenshot above, but state filtered means that the service itself is actually running and only access to the port is controlled. So, what about those two other open ports listening on TCP 49152 and 49153? Searching the net gives me mainly clues about Windows Vista/7 changed settings of user-assigned ports and there's another observation related to this device from someone in Mexico. My best guess is that it might be related to remote management service used by Mauritius Telecom to configure the modem. This is based on my observation that they were able to re-configure my VoIP settings remotely after I called the service centre. The Portable SDK for UPnP \"provides developers with an API and open source code for building control points, devices, and bridges...\" - yup, that confirms my idea. Reading Vulnerability Note VU#922681 - Portable SDK for UPnP Devices (libupnp) contains multiple buffer overflows in SSDP and linked papers gives me the chills! Devices that use libupnp may also accept UPnP queries over the WAN interface, therefore exposing the vulnerabilities to the internet. The vulnerability note reports that several manufacturers have been notified about this issue, Huawei is among them. Literally, any HG8245H modem by Mauritius Telecom can be accessed and exploited remotely without you knowing it. Thanks for that... Additional resources The video discusses more details on dnsmasq which I do not want to elaborate here. Feel free to watch the recording completely. And other bloggers wrote on the same topic. Check out the following articles online: Vulnerabilities on MT FTTH Routers Modem Insecurity - Hackers.mu Hackers.mu VideoStream #1 : Modem Insecurity in Mauritius Nitin mentions in his article that the configuration file can be downloaded and decrypted. Very interesting indeed. Fellow craftsman and hacker Bilaal went into a full analysis of every open source software used, and wrote a Vulnerability Compilation on Huawei HG8245H Routers with more details. Scary stuff! Feeling safe not using Mauritius Telecom but Emtel? Fear not, let's have a closer look... Huawei B68A provided by Emtel Some years back Emtel introduced a GSM gateway device to offer their customers more comfort and options than the USB dongles available at that time. Branded as Home & Office internet connectivity you received a device with 4 LAN ports, POTS outlet for a regular telephone and some wires. Emtel operates on the mobile network solely, and that device is a 2G/3G router for their network. The SIM card in the device is locked and therefore cannot be used in a regular mobile phone. The B68A device, also known as the black modem, is capable of transfer speeds of up to 21 Mbps. Initially, you would log into the device using the default credentials admin : admin. Next, you are requested to change the password for security reasons. Maybe you might interested in other default Router Username and Password for Huawei Routers. Although Huawei is the same manufacturer the user experience in the web interface is completely different compared to the modem used by Mautitius Telecom. By default there is no Telnet service active: $ telnet 192.168.1.1 Trying 192.168.1.1... telnet: Unable to connect to remote host: Resource temporarily unavailable And so far, I didn't see any options to enable Telnet. Also, there is no second account with higher permissions in that device. Next, I did a quick port scan and it also shows that only the necessary ports are open after all. PORT STATE SERVICE VERSION 53/tcp open tcpwrapped 80/tcp open http? 443/tcp open ssl/https? Be aware that an open port 53 for domain name service (DNS) might lead to an outdated dnsmasq software. Great to see that Huawei or Emtel uses an SSL certificate but accessing the B68A via HTTPS gives you a warning, like this: 192.168.1.1 uses an invalid security certificate. The certificate is not trusted because it was issued by an invalid CA certificate. The certificate is not valid for the name 192.168.1.1. The certificate expired on 22 May 2017, 05:55. The current time is 19 October 2017, 08:45. Error code: SEC_ERROR_CA_CERT_INVALID Further inspection of the SSL certificate might give you the following details. Clearly, this is a self-signed certificate. It's kind of surprising to see that it has been created back in 2007, long before this device even existed. Anyway, providing fewer features makes the device a bit safer compared to the HG8245H. Huawei B310S-927 provided by Emtel In recent times Emtel needed an alternative to their Airbox package, an attempt to provide Fiber-over-the-Air (FTTA) data connectivity. Well, a new product called WiFi Plus was created, and it is bundled with another GSM gateway which is capable to run on 4G mobile infrastructure. Given some low network coverage, continuous issues in regards to stable connectivity, and flaky Skype conversations I went to one of the Emtel showrooms to upgrade the above mentioned B68A device to the newer B310s model, aka the white modem. If you're having similar issue I highly recommend you to upgrade your device. You won't regret it. Note: You can attach the external antenna of the B68A to the second connector of the B310s and run the device with two external antennas for better coverage. Note: By disabling all internet-related features the B68A can still be used as a network switch. The default login experience using admin : admin is equivalent to the B68A and although there are more options Huawei packed less features into that modem. Meaning, there is no Telnet access active and there is definitely no second account with super administrative rights. PORT STATE SERVICE VERSION 53/tcp open tcpwrapped 80/tcp open http webserver 443/tcp open ssl/https webserver 8080/tcp open http-proxy webserver DNS service is available on the device, as expected, and there are three ports for a web interface. Accessing the HTTP address would be the standard. Navigating to the HTTPS address in Firefox gives you a hint regarding a network login, like this: Although interesting I didn't get anything useful out of this. And the proxy URL on port 8080 simply redirects you back to the regular HTTP address. Nothing to see here. Compared to the B68A modem the self-signed SSL certificate in the B310S is still valid, and can be added to your local certificate store for future reference. And at least the Common Name (CN) as well as the Organization (O) fields are better choices, too. Only drawback would be that the modem announces itself as homerouter.cpe in DNS instead of mobile.wifi which could lead to another SSL warning. Now what? The choice of internet connection is yours obviously. I'm using services of both major ISPs in Mauritius. Since years I was using Emtel exclusively but in regards to their failure to deliver Airbox in Flic En Flac before general availability of Fiber by Mauritius Telecom we decided to give it a try. With the incidents happened on the international routes, like defect switch in Monaco or damage of sea cable, I have to say that I welcome the possibility to choose between service providers. Emtel used to be my first choice but the stability and advantages of Fiber over the hassles observed on the mobile network changed my opinion. Either way, there seems to be no best choice (yet?) in Mauritius. Either you get higher latency on a mobile network infrastructure with lack of resilience, or you have to deal with flawed router hardware that needs special treatment and hardening. Alternatives are available... but not easy! Given the observations made above, I went down a different road. Actually, I purchased a Raspberry Pi 3 with integrated WiFi, installed a Linux distribution on it and converted the device into a transparent network bridge with additional services and features. Now, all other devices on the network connect to the Raspberry Pi first, which then routes internet traffic to the modem of the ISP. The configuration of the modem is down to the bare minimum, just enough to provide access to the interweb. What is your experience? Do you have a different approach? Anything missing in the article? Please, leave a comment and talk about your ideas and recommendations. Cover photo by Rucksack Magazine on Unsplash. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'router-in-mauritius.html'; this.page.identifier = '9271ed1c97_idrouter-in-mauritius'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "rss-validation.html": {
    "href": "rss-validation.html",
    "title": "RSS Validation - Get Blogged by JoKi",
    "summary": "RSS Validation Copy Markdown View Markdown GitHub Markdown Source RSS Validation Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'rss-validation'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'rss-validation'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'RSS Validation') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-07-10 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Endlich mal wieder ein wenig Zeit für die Entwicklung meines HTML Plugin für Active FoxPro Pages... Als Quintessenz dessen konnte ich ein paar Korrekturen an der CursorToRss() Methode realisieren, die es ermöglicht direkt aus einer Tabelle einen gültigen RSS 2.0 Feed zu erzeugen. Zur Kontrolle habe ich mir einen Onlinevalidator herangezogen, und das Ergebnis ist inzwischen zufriedenstellend: https://www.feedvalidator.org/check.cgi?url=http%3A%2F%2Fwww.afpwi ki.de%2Fafpwiki.afp Damit können nun auch Nutzer des AfpWiki von diesen Anpassungen ihre Vorteile ziehen. Also, Subversion-Client anwerfen und aktuelle Version ziehen. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'rss-validation.html'; this.page.identifier = '9271ed1c97_idrss-validation'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "running-vmware-player-on-linux-xubuntu-hardy-heron.html": {
    "href": "running-vmware-player-on-linux-xubuntu-hardy-heron.html",
    "title": "Running VMware Player on Linux (xubuntu Hardy Heron) - Get Blogged by JoKi",
    "summary": "Running VMware Player on Linux (xubuntu Hardy Heron) Copy Markdown View Markdown GitHub Markdown Source Running VMware Player on Linux (xubuntu Hardy Heron) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'running-vmware-player-on-linux-xubuntu-hardy-heron'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'running-vmware-player-on-linux-xubuntu-hardy-heron'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Running VMware Player on Linux (xubuntu Hardy Heron)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-05-28 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); A new article since a long time. Sorry, I was and still am very busy on my job but I need to write these lines first. Since last month I modified my main development system a little bit and instead of running a native Windows operating system I gave xubuntu 7.10 a try. For developing purposes I installed VMware Player and therefore run virtual machines with Windows XP and Windows Vista. That's way more practical then a multiboot system. By the way, you can do this as well under Windows directly and use Microsoft Virtual PC instead of VMware, too. As said, I started with xubuntu 7.10 and upgraded recently to 8.04 (Hardy Heron). Everything went smooth but VMware Player didn't start anymore. Fine, as VMware comes with predefined kernel modules which none of them fits for kernel version 2.6.24-16-generic you have to initiate the compile process yourself. No problem at all. Well, in general... Sadly, the compile did not finish and interrupted with an error message according to a wrongly included header file. So, I looked around a bit and found a nice step-by-step guide on the VMware website to get it up and running. Here are the steps: cd /usr/lib/vmware/modules/source cp vmmon.tar vmmon.tar.orig sudo tar xvf vmmon.tar cd vmmon-only/include/ sudo nano vcpuset.h (or use any other editor you prefer) change line 74 from: #include \"asm/bitops.h\"\u009D to: #include \"linux/bitops.h\"\u009D rm vmmon.tar (return to the folder where you decompressed the tar file) sudo tar cvf vmmon.tar vmmon-only/ sudo rm -rf vmmon-only/ sudo vmware-config.pl If you try to install a fresh tarball of VMware Player then the path to the vmmon.tar archive varies depending on your download. cd <path/to/your/dir>/vmware-player-distrib/lib/modules/source/ The modification is the same but the last steps differ: cd ../../../ sudo ./vmware-install.pl This process should work for VMware Server and Workstation as well. And this morning I had to do the same steps again due to an 'sudo apt-get dist-upgrade' that installed a new kernel version 2.6.24-17-generic. So, after all and now that you know how to get your VMware Player to fly, it's not a big deal. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'running-vmware-player-on-linux-xubuntu-hardy-heron.html'; this.page.identifier = '9271ed1c97_idrunning-vmware-player-on-linux-xubuntu-hardy-heron'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "running-vmware-server-on-linux-version-106-on-xubuntu.html": {
    "href": "running-vmware-server-on-linux-version-106-on-xubuntu.html",
    "title": "Running VMware Server on Linux (version 1.0.6 on xubuntu) - Get Blogged by JoKi",
    "summary": "Running VMware Server on Linux (version 1.0.6 on xubuntu) Copy Markdown View Markdown GitHub Markdown Source Running VMware Server on Linux (version 1.0.6 on xubuntu) Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'running-vmware-server-on-linux-version-106-on-xubuntu'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'running-vmware-server-on-linux-version-106-on-xubuntu'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Running VMware Server on Linux (version 1.0.6 on xubuntu)') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2008-06-03 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); After the last article about Running VMware Player on Linux I thought that it would be very nice to be able to create new and modify existing machines. Ergo, let's try the latest version of VMware Server on the same machine. Well, this installation doesn't need any modifications in source code but it is also not without pains. The main difference for sure is that the VMware server runs as a service - well, actually it is invoked by xinetd - and can be administrated locally and remotely. But let's focus on the installation first. As mentioned we need xinetd on our system. This is done via apt, aptitude or any other APT install client that you prefer: sudo apt-get install xinetd After that we just run the standard procedure. Unpacking the archive and running the installation script like so: tar xvzf VMware-server-1.0.6-91891.tar.gz cd vmware-server-distrib sudo ./vmware-install.pl The installation process is nearly the same as for the VMware Player. Additionally you have to agree the EULA and enter the serial number for the server. The serial number is provided by the registration on VMware's website. Alright, everything looks fine and we can try to fire up the VMware Server Console from the Applications :: System menu. In case that you don't get any reaction or feedback, open a terminal and run the command: vmware to see what's happening behind the scenes. I got several errors according to wrong version of gcc: /usr/lib/vmware/bin/vmware: /usr/lib/vmware/lib/libgcc_s.so.1/libgcc_s.so.1: version GCC_3.4' not found (required by /usr/lib/libcairo.so.2)<br />/usr/lib/vmware/bin/vmware: /usr/lib/vmware/lib/libgcc_s.so.1/libgcc_s.so.1: version GCC_4.2.0' not found (required by /usr/lib/libstdc++.so.6) /usr/lib/vmware/bin/vmware: /usr/lib/vmware/lib/libgcc_s.so.1/libgcc_s.so.1: version GCC_3.4' not found (required by /usr/lib/libcairo.so.2)<br />/usr/lib/vmware/bin/vmware: /usr/lib/vmware/lib/libgcc_s.so.1/libgcc_s.so.1: version GCC_4.2.0' not found (required by /usr/lib/libstdc++.so.6) /usr/lib/vmware/bin/vmware: /usr/lib/vmware/lib/libgcc_s.so.1/libgcc_s.so.1: version GCC_3.4' not found (required by /usr/lib/libcairo.so.2)<br />/usr/lib/vmware/bin/vmware: /usr/lib/vmware/lib/libgcc_s.so.1/libgcc_s.so.1: version GCC_4.2.0' not found (required by /usr/lib/libstdc++.so.6) and therefore I did a short research on the web to find a nice solution: cd /usr/lib/vmware/lib/libgcc_s.so.1 sudo mv libgcc_s.so.1 libgcc_s.so.1.org cd ../libpng12.so.0 sudo mv libpng12.so.0 libpng12.so.0.org Attention: The destination paths on your system might vary depending on your entries during the installation procedure. Renaming the library files provides access to the existing library files of your Linux system. After this little tweak I could run the VMware Server Console directly from the console and from the menu without any problems. Now, it is your time to enjoy and operate your virtual machines with VMware Server. Sincerely, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'running-vmware-server-on-linux-version-106-on-xubuntu.html'; this.page.identifier = '9271ed1c97_idrunning-vmware-server-on-linux-version-106-on-xubuntu'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "running-windows-applications-on-linux.html": {
    "href": "running-windows-applications-on-linux.html",
    "title": "Running Windows applications on Linux - Get Blogged by JoKi",
    "summary": "Running Windows applications on Linux Copy Markdown View Markdown GitHub Markdown Source Running Windows applications on Linux Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'running-windows-applications-on-linux'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'running-windows-applications-on-linux'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Running Windows applications on Linux') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-11-24 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Taking into consideration that the End of Support Lifecycle for Microsoft Windows XP is closing in one might be confronted with the question whether it would be possible to keep existing hardware and installed software applications without having the necessity to upgrade the operating system (which eventually might be incompatible or requires higher investment). The session provides a brief overview of potential solutions and demonstrates quickly how to install and run Windows software on Linux. Running Windows applications on Linux from Jochen Kirstätter if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'running-windows-applications-on-linux.html'; this.page.identifier = '9271ed1c97_idrunning-windows-applications-on-linux'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "runtimeinstaller-for-visual-foxpro-90-sp2-ctp.html": {
    "href": "runtimeinstaller-for-visual-foxpro-90-sp2-ctp.html",
    "title": "RuntimeInstaller for Visual FoxPro 9.0 SP2 CTP - Get Blogged by JoKi",
    "summary": "RuntimeInstaller for Visual FoxPro 9.0 SP2 CTP Copy Markdown View Markdown GitHub Markdown Source RuntimeInstaller for Visual FoxPro 9.0 SP2 CTP Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'runtimeinstaller-for-visual-foxpro-90-sp2-ctp'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'runtimeinstaller-for-visual-foxpro-90-sp2-ctp'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'RuntimeInstaller for Visual FoxPro 9.0 SP2 CTP') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-14 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); ProLib Software provides current runtime installers for Visual FoxPro on our Web servers at https://ftp.prolib.de/public/ and ftp://ftp.prolib.de/public/ Today we offer a new runtime installer for deployment of applications build on Visual FoxPro Service Pack 2 CTP as announced at Visual FoxPro: October 2006 - Letter from the Editor and available at Microsoft Visual FoxPro Sedna October 2006 CTP. Please feel free to download and use those runtime installers to deploy your own VFP solutions to your customers. You can integrate them into your own installation routines; also as silent operations. For further details on our runtime setups read the ReadMe_Runtime.txt: This ReadMe explains the usage and commandline-switches for the provided Microsoft Visual FoxPro Runtime installers. After you have installed the VFP runtime once, you then only need to just copy your Application into any directory and it works. If you would use additional thirdparty components like ActiveX controls, you still have to install them on your own. Any feedback is welcome... kind regards, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'runtimeinstaller-for-visual-foxpro-90-sp2-ctp.html'; this.page.identifier = '9271ed1c97_idruntimeinstaller-for-visual-foxpro-90-sp2-ctp'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "sap-arena.html": {
    "href": "sap-arena.html",
    "title": "SAP Arena? - Get Blogged by JoKi",
    "summary": "SAP Arena? Copy Markdown View Markdown GitHub Markdown Source SAP Arena? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sap-arena'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sap-arena'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'SAP Arena?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-08-28 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Jupp, am 3. September ist eine der Eröffnungsfeiern in der SAP Arena. Und JoKi wird dabei sein... Spontan gestern auf dem Rock im Brauhof bei der Privatbrauerei Bischoff ausgemacht, geht's am kommenden Samstag ab nach Mannheim. Dort ist wieder volles Musikprogramm angesagt... \uD83D\uDE0E *Am 03. September feiert die SAP ARENA ihre Eröffnung mit einer Benefizveranstaltung: Rolf Stahlhofens MANN`EM SOUL orchestra präsentiert eine bunte Truppe, die unter anderem aus den SÖHNEN MANNHEIMS, SASHA, PETER MAFFAY, den MASSIVEN TÖNEN, BADESALZ und BÜLENT CEYLAN besteht. Die Künstler spenden ihre Gagen dem Verein MENSCHEN AM FLUSS und auch im Ticketpreis ist eine Spende enthalten. Das Line-Up vervollständigen: Silbermond, Laith-Al-Deen, Roachford, Mousse T. Feat. Emma Lanford, Joana Zimmer, Cosmo Klein, Groove Guerilla, Sydney Youngblood, W4C und Uwe Ochsenknecht.* Weitere Details gibt's hier: https://www.saparena.de/index.php?db[seite][d][nr]=84 Leute, ich bin gespannt wie ein Flitzebogen... Das wird garantiert wieder gut abgehen. In diesem Sinne, have fun! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'sap-arena.html'; this.page.identifier = '9271ed1c97_idsap-arena'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "saparena.html": {
    "href": "saparena.html",
    "title": "SAP Arena! - Get Blogged by JoKi",
    "summary": "SAP Arena! Copy Markdown View Markdown GitHub Markdown Source SAP Arena! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'saparena'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'saparena'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'SAP Arena!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-07 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Okay, ganz nach Plan hat es leider nicht funktioniert, aber gestern (Dienstag) ging's dann doch auf eine Eröffnungsfeier der SAP Arena in Mannheim. Freundschaftsspiel zwischen der MERC Adler Mannheim und einer Auswahlmannschaft der DEL (Dream Team). Nach leichten Anfahrtsschwierigkeiten - nein, ich saß nicht am Steuer - konnten wir uns das UFO noch bei gutem Tageslicht erstmalig von außen zu Gemüte führen. Sieht wirklich gut aus. Auch mit der verkehrstechnischen Anbindung wurde einiges beachtet und ich glaube, dass die SAP Arena sehr schnell für Events jeglicher Art angenommen werden dürfte. Gegen 19:00 startete das abendliche Spektakel mit einer Huldigung der glorreichen Vergangenheit der Mannheimer Adler - Meistermacher, Meisterschaften und Friedrichspark. Und einer kurzen Zeitraffersequenz für die Nachvollziehbarkeit des Baus. Wirklich informativ. Und danach ging's ans Eingemachte - Eishockey vom Feinsten - und ich denke, dass ein Spielergebnis von 9:5 alleinstehend für sich spricht. Oder? if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'saparena.html'; this.page.identifier = '9271ed1c97_idsaparena'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "sapfunctions-und-weitere-experimente.html": {
    "href": "sapfunctions-und-weitere-experimente.html",
    "title": "SAP.Functions und weitere Experimente - Get Blogged by JoKi",
    "summary": "SAP.Functions und weitere Experimente Copy Markdown View Markdown GitHub Markdown Source SAP.Functions und weitere Experimente Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sapfunctions-und-weitere-experimente'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sapfunctions-und-weitere-experimente'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'SAP.Functions und weitere Experimente') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-08-24 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Szenario mit SAP R/3 Von SAP hat sicherlich schon jede/r Aktive in der Softwarebranche gehört oder gelesen. In unserem laufenden Projekt steht in den nächsten Tagen die Erstellung einer Kommunikationsschnittstelle zu SAP R/3 an. Glücklicherweise kann ich hierzu auf ein paar frühe Umsetzungen von unserem Kunden zurückgreifen; spart ein wenig Zeit bei der Einarbeitung in die eigentliche Materie. Hintergrund für diese Anforderung ist u.a. das Buchen von Dokumenten in SAP aus unserer VFP-Projektentwicklung. Ausgestattet mit dem vorhandenen, funktionierenden Code aus der Altanwendung, einigen Ideen und zwei, drei Büchern sehe ich dieser Aufgabe gegenwärtig sehr versonnen ins Auge. Meine ersten Experimente in Bezug auf Automation des SAP-Clients sehen schon ziemlich versprechend aus. Zumal ich auch durch eine vorangehende Recherche im Internet etliche Beispiele in ASP, VBScript und JavaScript finden konnte, welche meinen Lösungsansatz festigen. COM-Server von SAP SAP GUI bietet als Client zu SAP R/3 nicht nur ein visuelles Frontend an. Ebenso stehen für den geneigten Fuchsbändiger einige COM Type Libraries zur Verfügung, welche problemlos in VFP instanziiert und genutzt werden können. Diese Nutzung ist vergleichbar mit der OLE-Automation von Word oder Outlook. Man benötigt lediglich die richtige Literatur und IntelliSense. Als Ausgangspunkt dient uns die Objektreferenz auf SAP.Functions. Mit dieser Instanz können wir die Verbindung zum SAP Backend aufbauen und die gewünschten Funktionen samt Eingabe- und Rückgabeparameter ausführen und auswerten. Define Class CSapGui As Label oSap = .Null. oResult = .Null. Function Logon() Local loConn, loException, luReturn m.loConn = .Null. m.loException = .Null. m.luReturn = .Null. Try This.oSap = CreateObject(\"SAP.Functions\") m.loConn = This.oSap.Connection m.loConn.System = \"DEV\" m.loConn.ApplicationServer = \"SAPSERVER\" m.loConn.Client = \"100\" m.loConn.User = \"IOS\" m.loConn.Password = \"FoxPro\" m.loConn.Language = \"DE\" m.loConn.TraceLevel = 6 *--- Klasseneigenschaften verwenden... ;-) m.luReturn = m.loConn.Logon(0, .T.) Catch To m.loException Assert .F. Message \"SAP-Schnittstelle nicht bereit\" EndTry Return m.luReturn EndFunc Sofern wir nicht aus dem Try-Catch-Block rausfliegen und der Rückgabewert des Logon positiv ausfällt, sind wir am SAP-Backend authentifiziert und können weitere Aktionen laufen lassen. Calls in SAP auslösen Uns steht eine Liste der zu nutzenden Calls im SAP-Backend zur Verfügung. Interessanterweise verfügen diese über unterschiedliche Anzahl an Parametern. In Kombination mit einer Meta-Tabelle, die uns die notwendigen Daten zur einzelnen Funktion liefert, können wir mit der vorhandenen Objektreferenz direkt weiter arbeiten. Function Call(tcFunction As String) Local lcFunction, loFunction, luReturn m.lcFunction = Transform(Evl(m.tcFunction, \"IosTest\")) m.loFunction = .Null. m.luReturn = .Null. This.oResult = .Null. m.loFunction = m.loSap.Add(m.lcFunction) If Vartype(m.loFunction) == T_OBJECT Select(\"MetaSAP\") Set Order To Sortierung Ascending Scan For cFunction = m.lcFunction m.loFunction.Exports(cParaName) = cParaValue EndScan m.luReturn = m.loFunction.Call() This.oResult = m.loFunction.Tables *--- weitere Verarbeitung des Ergebnis (Collection) This.CallCompleted(m.lcFunction) *--- oder: RaiseEvent(This, \"CallCompleted\", m.lcFunction) EndIf EndFunc Function Logoff() This.oSap.Connection.Logoff() EndFunc Function CallCompleted(tcFunction As String) *--- virtual: Konkretisierung in Ableitung implementieren EndFunc Durch die Kapselung der grundsätzlichen Funktionalität in eine eigene Klasse erhalten wir sehr viel Freiheit in weiteren Nutzung. In Ableitungen können Spezialisierungen bzw. sogenannte High-Level-Methoden erstellt werden, die für den jeweiligen Anwendungsfall einfachere Möglichkeiten bieten. Beispiele hierfür wären etwa 'SapGuiKunde.Anlegen()' oder 'SapGuiRechnung.Buchen()' welche den eigentlichen Kommunikationsanteil mit dem SAP-Backend vor dem Zugriff des Entwicklers kapseln und auf diese Weise die Verwendung erheblich vereinfachen. Eine Frage des Betrachtungswinkels Beim Erstellen einer generischen, wiederverwendbaren Klasse gilt es unter anderem darauf zu achten, dass die sogenannten Kernfunktionen implementiert werden. Jedoch nicht mehr. Das obige Beispiel dürfte sich gut dafür eignen. Zur weiteren Vereinfachung könnte man sogar noch eine Prozessmethode erstellen, welche den Gesamtablauf vereinfacht: Function Process(tcFunction As String) With This If .Logon() .Call(m.tcFunction) .Logoff() EndIf EndWith EndFunc In den Ableitungen dieser Klasse konzentriert man sich primär auf die Verarbeitung der Rückgabewerte aus dem SAP-Backend - eventuell mit Fallunterscheidung nach Funktion - und erstellt bei Bedarf weitere Methoden für Spezialisierungen. Weitere Nutzung der Klasse Durch den Verzicht auf konkrete Anforderungen innerhalb der Anwendung und der Umsetzung als neutrale Komponente sind wir spielend in der Lage die Klasse in den unterschiedlichsten Anwendungsfällen als Grundlage zu verwenden. Der aktuelle Entwurf sieht vor, dass jeweils in der Ableitung bzw. Instanz der Klasse die benötigte Lösung implementiert wird. Ich möchte übrigens erwähnen, dass der beschriebene Quellcode übrigens die dritte Revision der Klasse ist, da ich mit steigendem Wissen über die SAP.Functions direkt den bereits geschriebenen Code dem Refactoring - also 'Wegwerfen und neu schreiben' \uD83D\uDE0E - unterworfen habe. Ich möchte euch dazu ermahnen, dass noch kein Meister vom Himmel gefallen ist und ebenso ermuntern neue Ideen nicht sausen zu lassen, nur weil euer Code bereits im Einsatz ist. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'sapfunctions-und-weitere-experimente.html'; this.page.identifier = '9271ed1c97_idsapfunctions-und-weitere-experimente'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "sapiens-a-brief-history-of-mankind.html": {
    "href": "sapiens-a-brief-history-of-mankind.html",
    "title": "Sapiens: A Brief History of Humankind - Get Blogged by JoKi",
    "summary": "Sapiens: A Brief History of Humankind Copy Markdown View Markdown GitHub Markdown Source Sapiens: A Brief History of Humankind Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sapiens-a-brief-history-of-mankind'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sapiens-a-brief-history-of-mankind'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Sapiens: A Brief History of Humankind') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2023-09-26 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Thought-provoking and eye-opening book about the origin, the evolution and destructive nature of homo sapiens. However there seems to be hope of improvement. Or the danger of spreading further to other planets. In his book Sapiens: A Brief History of Humankind, Yuval Noah Harari argues that humans have been a plaque to the flora and fauna of Earth since our earliest days as hunter-gatherers. However, it was the Agricultural Revolution, which began around 11,000 years ago, that really allowed humans to start taking over the planet and wreaking havoc on the natural world. With agriculture, humans were able to produce more food than they needed, which led to population growth and the rise of cities. This, in turn, led to deforestation, overgrazing, and the extinction of many animal species. Harari notes that humans have caused more extinctions in the past 10,000 years than any other species in the history of Earth. The Industrial Revolution, which began in the 18th century, only made things worse. With new technologies, humans were able to exploit natural resources at an unprecedented rate. This led to even more pollution and deforestation, as well as the extinction of even more animal species. Today, humans are the dominant species on Earth. We have altered the planet's climate, polluted its oceans, and destroyed its forests. We are also responsible for the extinction of many animal species. Complementary to Hariri's observation I would like to make the connection to the blockbuster movie The Matrix where Agent Smith while questioning Morpheus has quite a similar conclusion about the human species. Agent Smith believes that humans are a virus because they consume the planet's resources without giving anything back. He says that humans are \"a cancer on the face of the Earth\" and that they must be eliminated. Harari, on the other hand, takes a more nuanced view. He acknowledges that humans have caused a lot of damage to the planet, but he also believes that we are the only ones who can stop it. He calls on us to use our intelligence and compassion to create a more sustainable future for ourselves and the planet. Another similarity between the two views is that they both see humans as being different from other animals. Agent Smith believes that humans are unique because they are the only species that is aware of its own destructive impact on the planet. Harari, on the other hand, believes that humans are unique because we are the only species that has the capacity to change its behavior. The main difference between the two views is that Agent Smith believes that humans are a threat to the planet and that they must be eliminated. Harari, on the other hand, believes that humans are the only ones who can save the planet.Agent Smith believes that humans are a virus because they consume the planet's resources without giving anything back. He says that humans are \"a cancer on the face of the Earth\" and that they must be eliminated. Harari, on the other hand, takes a more nuanced view. He acknowledges that humans have caused a lot of damage to the planet, but he also believes that we are the only ones who can stop it. He calls on us to use our intelligence and compassion to create a more sustainable future for ourselves and the planet. Another similarity between the two views is that they both see humans as being different from other animals. Agent Smith believes that humans are unique because they are the only species that is aware of its own destructive impact on the planet. Harari, on the other hand, believes that humans are unique because we are the only species that has the capacity to change its behavior. The main difference between the two views is that Agent Smith believes that humans are a threat to the planet and that they must be eliminated. Harari, on the other hand, believes that humans are the only ones who can save the planet. He calls on us to use our intelligence and compassion to create a more sustainable future for ourselves and the planet. Here are some specific examples of the ways in which humans have been a plaque to the flora and fauna of Earth: Overhunting: Humans have hunted many animal species to extinction, including mammoths, woolly rhinos, and saber-toothed cats. Deforestation: Humans have cleared vast areas of forest to make way for agriculture, urban development, and logging. This has destroyed the habitat of many animal species and contributed to climate change. Pollution: Humans have polluted the air, water, and soil with chemicals and other pollutants. This has harmed many plant and animal species. Climate change: Human activities, such as the burning of fossil fuels, have caused the Earth's climate to change. This is having a devastating impact on many plant and animal species. Harari argues that humans have a moral obligation to protect the planet and its biodiversity. He calls on us to reduce our impact on the environment and to live more sustainably. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'sapiens-a-brief-history-of-mankind.html'; this.page.identifier = '9271ed1c97_idsapiens-a-brief-history-of-mankind'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "schlafes-bruder.html": {
    "href": "schlafes-bruder.html",
    "title": "Schlafes Bruder - Get Blogged by JoKi",
    "summary": "Schlafes Bruder Copy Markdown View Markdown GitHub Markdown Source Schlafes Bruder Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schlafes-bruder'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schlafes-bruder'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Schlafes Bruder') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-29 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); So, wie aus den Blogs von Thomas und Golo leicht ersichtlich sein dürfte, bin ich heute mal etwas früher oder später - je nach relativer Zeitbetrachtung - nach Hause gekommen. Für mich stellt sich dabei sogleich die Frage: Schlaf oder Buch? Ich hab' mich noch nicht wirklich entschieden, aktuell tendiere ich für Buch in der Badewanne... Bezugnehmend auf den Blogtitel möchte ich herausstellen, dass der Roman wie auch dessen Verfilmung ein gelungenes Werk der Belletristik darstellen. Absolut empfehlenswert. Guten Morgen, Deutschland. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'schlafes-bruder.html'; this.page.identifier = '9271ed1c97_idschlafes-bruder'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "schlaflos-in----.html": {
    "href": "schlaflos-in----.html",
    "title": "Schlaflos in ... - Get Blogged by JoKi",
    "summary": "Schlaflos in ... Copy Markdown View Markdown GitHub Markdown Source Schlaflos in ... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schlaflos-in----'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schlaflos-in----'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Schlaflos in ...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-05-05 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Dümdidüm... Irgendwas läuft in meinem Tagesablauf verkehrt. Oder wie sonst könnte man die Beantwortung von lockeren 20 bis 30 Beiträgen in Foren und Newsgruppe um diese Uhrzeit erklären. Vielleicht ist es die Orientierung an den Men In Black zu ernst genommen. Aber was soll man auch grossartig machen, wenn man weder müde ist, noch einschlafen kann? - Logisch, Community! Zwar schaue ich auch tagsüber in die Newsgruppen, insbesondere in unsere AFP Newsgruppe sowie die öffentlichen und privaten bei Microsoft, aber der eigentliche Spass in den Foren kommt nach dem Tageswerk. Und so geschieht es eben auch, dass man in den frühmorgendlichen Stunden noch munter Antworten, Tipps und Code in die Weiten des Internets hinaustackert. Hm, irgendwie rentiert es sich nun auch nicht mehr zu pennen. Dann eben mit dem ersten Zug wieder auf die Arbeit und dort noch ein wenig PowerNapping bis die Kollegen da sind... \uD83D\uDE0E Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'schlaflos-in----.html'; this.page.identifier = '9271ed1c97_idschlaflos-in----'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "schreiben-an-die-gez.html": {
    "href": "schreiben-an-die-gez.html",
    "title": "Schreiben an die GEZ - Get Blogged by JoKi",
    "summary": "Schreiben an die GEZ Copy Markdown View Markdown GitHub Markdown Source Schreiben an die GEZ Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schreiben-an-die-gez'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schreiben-an-die-gez'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Schreiben an die GEZ') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-10-02 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Herrje, ist schon phantastisch, was man alles in seinen Unterlagen findet. Hier mal ein Schreiben an die GEZ zwecks Meldung von Rundfunkempfängern (oder eben nicht). Das Schreiben wurde am 24.08.2001 verfasst: *Sehr geehrte Damen und Herren, vielen Dank für Ihre nette Aufmerksamkeit, mir vor Augen zu führen, dass ich mich am Rande der legalen Gesellschaft bewege. Strom, Wasser, Heizung, Telefon, Internet, etc. - wollen Sie weitere Ressourcen? - stehen mir auch jeden Tag rund um die Uhr zur Verfügung. Dennoch zahle ich ausschliesslich nur fpr den Umfang der Dienstleistung, die ich auch in nspruch nehme. Leider ist es mir zu keinem Zeitpunkt meines Alltags möglich, irgendeine Ihrer Dienstleistungen zu erkennen, die ich in Anspruch obwohl sie mir - sollte ich sagen: dankenswerterweise - von Ihnen zur Verfügung gestellt werden, nehme. Somit stelle ich Ihnen die folgende, etwas bürgerlich anmutende Frage, ob Sie in einem Restaurant ein Bier bezahlen, dass Sie weder bestellt noch konsumiert haben, nur weil es vom Gastwirt Ihnen zur Verfügung gestellt wird? Desweiteren ein paar Anmerkungen zu Ihren Informationen für den Privathaushalt: Angabe bzgl. nichtehelichen Lebensgemeinschaften, die aber in Ihrem Antwortschreiben nicht gewählt werden können. - soll ich lügen? Welche Vorteile sollte das Lastschriftverfahren gegenüber einem Dauerauftrag haben? Welche Aufgaben hat die GEZ überhaupt? Welcher Sinn steckt hinter den Gebühren, dass dafür extra eine Einzugszentrale aktiv sein muss? Ein weiterer Aspekt, der mich brennend interessiert: Wie kommen Sie an meine Adresse? Ich wüsste nicht, dass ich Ihnen diese zukommen liesse. Oder sind Sie als staatliche Instanz zu verstehen, die einfach mal so die Gemeinden nach neuen Adressen aushorcht? Somit ergeben sich für mich erhebliche Zweifel im Umgang mit dem Datenschutzgesetz. Es wäre sehr zuvorkommend von Ihnen, wenn Sie mich bitte über Ihre rechtliche Aktionsbasis informieren würden. Aber bitte keine lapidare Antwort in dem Stile, dass ich mir die Informationen bei Banken und Sparkassen selbst organisieren könnte... Mit freundlichem Gruß, Jochen Kirstätter PS: Deutschland ist ein Rechtstaat mit dem Rechtverständnis \"Im Zweifel für den Angeklagten\" Ihre unterschwellige Intension des Schreibens stellt aber jeden Bürger dieses Staates als Verbrecher hin, der seine Unschuld gefälligst zu beweisen hat. Mit Verlaub grenzt das bereits an Unterstellung bzw. Verleumdung. * Als Resonanz erhielt ich damals ein Schreiben, dass neben der Informationsquelle meiner Adresse - die Bertelsmann AG erweist sich als verantwortlich - auch den Rundfunkgebührenstaatsvertrag (RGebStV) enthielt. Sehr freundlich. Ausgehend von der aktuellen Situation, dass ab 1.1.2007 alle Computer mit Internetanschluss gebührenpflichtig sein sollen, frage ich mich auch heute wieder, was der Humbug eigentlich soll. Sorry, aber hier werden Informationen und Gebühren eingefordert, die keinerlei Relevanz besitzen. Das Internet ist nicht auf die öffentlich-rechtlichen Sendeanstalten beschränkt, bzw. deren Anteil - oder sollte man besser sagen: deren Verschmutzung? - ist marginal und irrelevant. Letztendlich läuft es auf einen großem Nepp hinaus. Oder wie soll man sonst die Verschwendung von Geld durch Werbespots im Kino verstehen? Hallo? Im Kino bin ich bereits zahlender Kunde für eine gewollte Dienstleistung und möchte mir nicht noch die Unterstellungen reinziehen, dass ich ein Raubkopierer und/oder \"Schwarzempfänger\" sei. Hm, eigentlich sollte man diese Diskrimierung bzgl. der Hautfarbe von Menschen öffentlich diskutieren... Scheiss-Rassismus! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'schreiben-an-die-gez.html'; this.page.identifier = '9271ed1c97_idschreiben-an-die-gez'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "schreiben-sie-eine-online-rezension.html": {
    "href": "schreiben-sie-eine-online-rezension.html",
    "title": "Schreiben Sie eine Online-Rezension - Get Blogged by JoKi",
    "summary": "Schreiben Sie eine Online-Rezension Copy Markdown View Markdown GitHub Markdown Source Schreiben Sie eine Online-Rezension Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schreiben-sie-eine-online-rezension'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'schreiben-sie-eine-online-rezension'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Schreiben Sie eine Online-Rezension') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-04-06 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nach der Erstellung meiner ersten Rezension für die deutschsprachige FoxPro User Group letzten Monat habe ich mir vorgenommen, dass ich für jedes Buch, dass durch meine Badewanne geht... ...künftig eine Rezension schreiben werde. Ich finde das ziemlich fair gegenüber dem Verlag und gegenüber dem Autor, oder? Well, da ich zwecks meiner Experimente zu VFP Datenbank in .NET ein wenig Schützenhilfe hatte, nahm ich dies als Anlass um eine weitere Rezension zu schreiben: Net for Visual FoxPro Developers von Kevin McNeish, Cathi Gero Zuerst hatte ich mir gedacht: Ups, da hast du aber wenig geschrieben. Zudem noch eine deutsche Rezension zu einem englischsprachigen Buch, aber hey, es ist im deutschsprachigen Angebot von Amazon, also who cares... Für den zweiten Versuch scheint's schon akzeptabel zu sein... oder sonst hätte es Amazon wohl nicht auf meine Mitbürger losgelassen. Ich hab' am Schreiben von Buchrezensionen Gefallen gefunden. IMHO fördert es die Reflexion über das Gelesene. Was letztendlich zu einer Vertiefung des Memorierens führt - zumindest hoffe ich das. Damit geht's mit ein wenig Übung auf die Rezensionen für die Bücher aus dem Hause O'Reilly. Ich bin schon richtig gespannt, wann die erste Auslieferung für die VFP Usergroup erfolgt. Für den ersten Lauf haben wir in hüstel demokratischer Entscheidung folgende Titel angefordert: SQL in a Nutshell (Q2/2005) Versionskontrolle mit Subversion Pragmatic Version Control Using Subversion Hm, eigentlich sollten die Bücher schon eingetroffen sein... Naja, später mal per Mail nachfragen. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'schreiben-sie-eine-online-rezension.html'; this.page.identifier = '9271ed1c97_idschreiben-sie-eine-online-rezension'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "screenauflsung-5184x1200.html": {
    "href": "screenauflsung-5184x1200.html",
    "title": "Screenauflösung 5184x1200? - Get Blogged by JoKi",
    "summary": "Screenauflösung 5184x1200? Copy Markdown View Markdown GitHub Markdown Source Screenauflösung 5184x1200? Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'screenauflsung-5184x1200'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'screenauflsung-5184x1200'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Screenauflösung 5184x1200?') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-09-22 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Nun... klingt schon mal Irrsinniges, aber ich meine, dass wir gestern auf dem Speyerer Stammtisch mit nur zwei Laptops und Beamer auf diese Auflösung kamen, oder Thomas? Zumindest war es sehr witzig auf einem StiNo-Laptop die Auswahl auf 4 physikalische Screens zu haben. Okay, wie haben wir das produziert? Mit Hilfe zweier schnuckeliger Tools, die man auf alle Fälle haben sollte. Als ersten Kandidat für Laptops ein Muss ist UltraMon. Es handelt sich dabei um ein Addon zur vereinfachten Handhabung von multiplen Monitoren. Beim Laptop ist das insbesondere bei Verwendung mit Beamer essentiell. Wir haben hier in der Firma inzwischen jeden Laptop mit einer UltraMon-Lizenz ausgestattet und wenn man das Teil mal in Aktion gesehen hat, dann möchte man es auch nicht mehr missen. Der zweite Verantwortliche nennt sich MaxiVista und ermöglicht die Extension des Bildschirms auf weitere Rechner per Netzwerk. Ein phantastisches Tool! Neben der reinen Extension gibt's zusätzlich noch den Remote Control Mode, der eine visuelle Kontrolle über den entfernten Rechner ermöglicht. Das macht wirklich Spass! MaxiVista installiert dazu auf den Rechnern - Primary bzw. Secondary - einen virtuellen Grafikkartentreiber, welcher wahrscheinlich ähnlich dem X11-Protokoll bzw. NX-Protokoll im Netzwerk agiert. Spassigerweise kann man ebenfalls einen Linux-PC als Secondary verwenden, sobald man den Treiber in wine installiert hat. Die Performance ist ein wenig langsamer als nativ unter Windows, aber zumindest der Extension Mode funktioniert stressfrei. Zurück zu unserer Monsterauflösung: Mein Laptop mit einer Default-Auflösung von 1600x1200, dann per UltraMon einen zweiten aktiviert, der ursprünglich der Beamer war. Da wir aber dann den Beamer an Thomas Laptop anschlossen, konnten wir dann per MaxiVista dort nochmals zwei Screens rekrutieren. Irgendwie krank, aber letztendlich hatten wir dann 4 'physikalische' Monitore, obwohl nur 3 Ausgabegeräte (2 TFT plus 1 Beamer) vorhanden waren. Aber sah' schon spassig aus. Vorallem lassen sich Screens an beliebigen Stellen eines virtuellen Gesamtdesktops platzieren, was auch für kleinere Lacher gesorgt hat. Dennoch halte ich beide Tools - UltraMon und MaxiVista - für einen PC-Nutzer, der einen Laptop und/oder ein Netzwerk mit mehreren Computern sein eigen nennen darf, als Pflichtsoftware. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'screenauflsung-5184x1200.html'; this.page.identifier = '9271ed1c97_idscreenauflsung-5184x1200'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "semitransparente-forms-erstellen.html": {
    "href": "semitransparente-forms-erstellen.html",
    "title": "Semitransparente 'Forms' erstellen - Get Blogged by JoKi",
    "summary": "Semitransparente 'Forms' erstellen Copy Markdown View Markdown GitHub Markdown Source Semitransparente 'Forms' erstellen Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'semitransparente-forms-erstellen'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'semitransparente-forms-erstellen'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Semitransparente 'Forms' erstellen') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-29 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Ausgehend von meinem letzten Beitrag zu Projekt Sunshine möchte ich heute ein paar Tipps & Tricks darüber geben, wie man den Semitransparenz-Effekt selbst produzieren kann. Gleich vorab möchte ich sagen, dass diverse Lösungsansätze nicht von mir stammen, sondern aus diversen Blogs der VFP-Community entlehnt sind. Ich habe lediglich die verschiedenen Teillösungen ein wenig zusammengeschrieben und daraus generische Komponenten erzeugt. Oder zumindest versucht... \uD83D\uDE01 Als Programmierbasis verwende ich VFP 9.0 SP1 und unsere Komponentenbibliothek Acodey, welche in diesem Zusammenhang lediglich einige Helfermethoden zur Verfügung stellt, welche kein Hindernis zur Verwendung ohne Acodey darstellen. Transparenz in VFP-Forms realisieren Die Erzeugung von Transparenz, oder besser gesagt Opacity, bei einer VFP-Form geht mittels der Windows 32 API relativ einfach. Man braucht dazu lediglich zwei API-Funktionen und schreibt die folgenden Zeilen etwa in die Init-Methode einer Form: #Define GWL_EXSTYLE -20 #Define WS_EX_LAYERED 0x80000 Declare SetWindowLong In WIN32API ; Integer, Integer, Integer Declare SetLayeredWindowAttributes In WIN32API ; Integer, String, Integer, Integer Local lnOpacity m.lnOpacity = 255 * This.nOpacity / 100 && This.nOpacity = 75 (%) SetWindowLong(This.HWnd, GWL_EXSTYLE, WS_EX_LAYERED) SetLayeredWindowAttributes(This.HWnd, 0, m.lnOpacity, 2) Sieht ja schon mal recht einfach und effektiv aus. Nur gibt's dabei leider ein Problem. Erstens funktioniert es nur bei Top-Level-Formularen (ShowWindow = 2) und es werden sämtliche Controls auf dem Formular gleichermaßen transparent angezeigt. Sobald die Form In-Screen verwendet wird, haben die Codeanweisungen keine Wirkung. Well, zumindest kann man damit stylische Popups oder Tooltipps schreiben. Insbesondere wenn man den Opacity-Grad mittels Timer verändert. Anbei mal ein Screenshot mit dem Ergebnis: Opacity-Effektiv bei VFP-Form mit Thisform.ShowWindow = 2 In den mitgelieferten Solution Samples in VFP ist übrigens ein entsprechendes Beispiel zu finden. Do Home(2) + \"solutionsolution.app\" Dort dann unter Forms :: Transparent forms schauen. Formulare vor der Kamera Der zweite Ansatz besteht in der Anwendung des Blue-/Green-Screen Verfahrens wie man es bei der Filmproduktion kennt. Die Grundlage des Verhahrens ist, dass man diverse Regionen eines Formulars mit einem bestimmten Farbwert - etwa Rgb(0,0,255) oder Rgb(0,255,0) - maskiert und diese Regionen wiederum mittels Win32 API Funktionen aus dem Formular rausschneidet und damit Platz für den Hintergrund schaffen kann. Diesen Lösungsansatz habe ich zuerst bei VFPSkin entdecken können. Aber auch andere VFP-Coder in der Community, namentlich Craig Boyd und Bernard Bout, haben bereits damit experimentiert. In den mitgelieferten Solution Samples von Visual FoxPro findet man ebenfalls einen Verweis auf diese Technik. Unter New in Visual FoxPro 8.0 :: Creating Irregularly shaped windows findet sich de entsprechende Quellcode für die Umsetzung. Die Lösungen von VFPSkin und Craig Boyd funktionieren jedoch auch für In-Screen-Formulare. Soweit sieht das ja schon ganz gut aus, aber... der eigentliche Effekt der Semitransparenz haben wir bisher noch nicht erreichen können. Und das größte Problem ist, dass die transparenten Stellen des Formulars immer transparent, also auch Controls 'verschwinden'. Bernard Bout beschreibt in seinem Artikel Visual Foxpro shows its Glass! zwar die Vorgehensweise unter Verwendung von zwei überlagerten Formularen, aber ich halte das für zu aufwendig. Letztendlich ist es Ansichtssache. Controls mit BackStyle = 0 VFP bietet von Haus aus bereits Transparenz für einige Controls an, leider befindet die Form-Klasse nicht darunter. Es wäre ja auch zu einfach... \uD83E\uDD2A Wenn man jedoch bedenkt, dass eine Form lediglich ein visueller Container mit ein paar netten Eigenschaften wie Titlebar samt Buttons, Datenumgebung, Beweglichkeit und Größenveränderung ist, dann kommt man doch recht schnell auf die Idee einen 'Form-Container' zu erstellen. Die Vorteile liegen meines Erachtens klar auf der Hand. Man erstellt sich eine generische Basisklasse für diesen Container und simuliert die Funktionsweise eines Formulars. Diesem verpasst man eine eigene Titlebar - hier einfach ein Shape -, ein Icon und die ControlButtons (Minimize, Maximize und Close). Generische Basisklasse für Container-basierte 'Form' Weiterhin stattet man dann den Container mit einigen Eigenschaften, wie ein Formular aus: Caption (mit Assign), Icon (mit Assign), Movable, nXCoord und nYCoord (wird für MouseMove gebraucht). Dazu kommen noch ein paar Methoden wie Show, Hide, EventClick und EventMouseMove und fertig ist die Basisklasse. Warum der ganze Aufwand eigentlich? Nun, der Trick hierbei liegt in der Möglichkeit, dass man auf diese Art und Weise sowohl vollständige Freiheit in der optischen Gestaltung von 'Formularen' besitzt und absolut problemlos mit Opacity in diversen Teilbereichen des Containers arbeiten kann. Den Container mit den Formfähigkeiten ausstatten Damit unser Container das Look & Feel einer Form erhält, müssen wir noch ein paar Zeilen Code in die Basisklasse schreiben. Fangen wir mit den einfachen Dingen, wie etwa Caption und Icon an: Procedure This.Caption_Assign *=================================================== * Weitergabe des Parameters an das zuständige Control. *=================================================== Lparameters vNewVal This.lblCaption.Caption = m.vNewVal This.Caption = m.vNewVal EndProc Procedure This.Icon_Assign *=================================================== * Weitergabe des Parameters an das zuständige Control. *=================================================== Lparameters vNewVal This.imgIcon.Picture = m.vNewVal This.imgIcon.Visible = .T. This.Icon = m.vNewVal EndProc Dies ermöglicht sowohl im Init wie auch zu späteren Zeitpunkten die einfache Veränderung dieser beiden Eigenschaften. Als nächstes wollen wir sicherstellen, dass unser Container verschiebbar wird. Dazu implementieren wir die verschiedenen Mouse-Methoden des Shapes, welches unsere Titlebar simulieren wird: Procedure This.shpTitlebar.MouseDown LPARAMETERS nButton, nShift, nXCoord, nYCoord This.Parent.nXCoord = m.nXCoord - Objtoclient(This.Parent, OBJTOCLIENT_LEFT) This.Parent.nYCoord = m.nYCoord - Objtoclient(This.Parent, OBJTOCLIENT_TOP) EndProc Procedure This.shpTitlebar.MouseUp LPARAMETERS nButton, nShift, nXCoord, nYCoord This.Parent.nXCoord = 0 This.Parent.nYCoord = 0 EndProc Procedure This.shpTitlebar.MouseLeave LPARAMETERS nButton, nShift, nXCoord, nYCoord This.Parent.nXCoord = 0 This.Parent.nYCoord = 0 EndProc Procedure This.shpTitlebar.MouseMove LPARAMETERS nButton, nShift, nXCoord, nYCoord This.Parent.EventMouseMove(nButton, nShift, nXCoord, nYCoord) EndProc Selbstverständlich kann dies auch per BindEvent()-Funktion gelöst werden. Dabei jedoch an den fünften Parameter (nFlags = 1) denken. Die eigentliche Umsetzung der Bewegung findet dann wieder im Container statt: Procedure This.EventMouseMove *=================================================== * Bewegung des Containers als Reaktion der Mausschubserei umsetzen *=================================================== Lparameters tnButton, tnShift, tnXCoord, tnYCoord With This If .Movable If Bittest(m.tnButton, 0) .And. ; Not (.nXCoord == 0 .Or. .nYCoord == 0) This.Move( ; m.tnXCoord - .nXCoord, ; m.tnYCoord - .nYCoord, ; .Width, .Height ; ) EndIf EndIf Endwith EndProc Sofern der Container als beweglich konfiguriert ist, führen wir die entsprechende Aktion auch durch. Warum die Verlagerung auf den Container? Ganz einfach, es könnten ja noch weitere Controls das Moven auslösen, oder die Titlebar setzt sich aus mehreren Controls (Images oder so... ) zusammen. Für unseren Basiscotainer gehen wir von einer rechteckigen Titlebar aus und arbeiten jetzt noch ein wenig mit der ZOrder des Shapes. Das Shape sollte auf alle Fälle über dem Caption-Label und dem Icon-Image liegen, jedoch unter dem Controlbox-Image, da die Buttons ja noch klickbar sein sollen. Apropos Controlbox... dieser wenden wir uns jetzt zu. In seinem Artikel Using the Alpha Channel in Visual Foxpro Images verwendet Bernard Bout unsichtbare Commandbuttons (übrigens Style = 1 und nicht Visible = .F.) für die Realisierung des gewünschten Effekts. Das erscheint mir ehrlich gesagt überflüssig, da das Image-Control über eine Click-Methode verfügt und man mittels der Funktion ObjToClient() rausfinden kann, wo auf dem Image geklickt wurde. Das Konzept ähnelt dabei einer ImageMap aus HTML; Regionen können unterschiedliche Aktionen auslösen. In VFP-Code sieht das Ganze dann so aus: Procedure This.imgControlBox.Click *=================================================== * Click-Ereignisse auf die Grafik 'simulieren'. * Wir verwenden eine Fallunterscheidung auf Basis der Position des * Mausklicks, und entscheiden damit, welches 'Ereignis' ausgelöst wird. * Zur Orientierung bestimmen wir die linke Ecke der VistaButtons. *=================================================== With This Local lnLeftMin, lnLeftMax, lnLeftClose, lnPosition m.lnLeftMin = ObjToClient(This, OBJTOCLIENT_LEFT) m.lnLeftMax = m.lnLeftMin + 25 && Breite des MinButton m.lnLeftClose = m.lnLeftMax + 25 && Breite des MaxButton m.lnPosition = Mcol(0, 3) Do Case Case Between(m.lnPosition, m.lnLeftMin, m.lnLeftMax) This.Parent.EventClick(\"Minimize\") Case Between(m.lnPosition, m.lnLeftMax, m.lnLeftClose) This.Parent.EventClick(\"Maximize\") Otherwise This.Parent.EventClick(\"Close\") EndCase EndWith EndProc Auch hier wieder die Ursache warum ich das so mache. Erstens, braucht man weniger Controls, die zwei bzw. drei CommandButtons sind schlichtweg überflüssig und man kann auf diese Weise auch wiederum irregulare Shapes verwenden. Selbst bei Nutzung von einzelnen Grafiken zur Darstellung der Buttons braucht es lediglich deren Click-Methode. Man denke hierbei an zusätzliche Form-Funktionalitäten wie etwa Menü, Stick und Shade (Linux-Kenner wissen Bescheid). Die Reaktion auf die Buttons wird wiederum im Container in der Methode EventClick implementiert: Procedure This.EventClick *=================================================== * Reaktion auf die Click-Ereignisse der VistaButtons. *=================================================== Lparameters tcSource With This Local lcSource m.lcSource = Lower(Alltrim(Evl(m.tcSource, \"\"))) *------------------------------------------------------------- * Fallunterscheidung nach Click-Quelle * kann in Ableitungen überlagert und erweitert werden *------------------------------------------------------------- Do Case Case m.lcSource == \"ok\" .Hide() Case m.lcSource == \"cancel\" .Hide() Case m.lcSource == \"minimize\" Case m.lcSource == \"maximize\" Case m.lcSource == \"close\" .Hide() Case m.lcSource == \"menu\" Case m.lcSource == \"stick\" Case m.lcSource == \"shade\" Otherwise EndCase EndWith Selbstverständlich können auch andere Controls mit Click oder MouseDown-Ereignis auf diese Verarbeitungsmethode des Containers umgeleitet werden. Damit kann man auch die Synchronisierung gleichen Verhalten erreichen. Im OK- bzw. Abbrechen-Button ruft man lediglich parametrisiert This.EventClick auf und das war's. Somit haben wir die Grundfunktionalität des Containers erzeugt. Werfen wir noch einen Blick in die Init-Methode, in der einiges zusammengesteckt wird: Procedure This.Init *=================================================== * Initialisierung der Container-Form auslösen. *=================================================== Lparameters toForm With This Local llOk m.llOk = .T. *------------------------------------------------------------------ * Assertions und defensive Programmierung *------------------------------------------------------------------ Assert Inlist(Vartype(m.toForm), T_OBJECT, T_OPTIONAL) If Vartype(m.toForm) == T_OBJECT This.oForm = m.toForm Endif m.llOk = DoDefault() If m.llOk .Caption = .Caption .Icon = .Icon .shpTitleBar.BorderStyle = 0 If Not .lCustomTitlebar .shpTitleBar.Move(0,0,.Width,.shpTitleBar.Height) EndIf .shpTitleBar.Anchor = 11 .imgControlBox.Left = .Width - .imgControlBox.Width - 7 .imgControlBox.Anchor = 9 EndIf If m.llOk Bindevent(This.lblCaption, \"MouseMove\", This, \"EventMouseMove\", 1) Bindevent(This.shpTitleBar, \"Click\", This, \"Show\") Bindevent(This.lblCaption, \"Click\", This, \"Show\") EndIf If m.llOk m.llOk = This.CreateBackground() EndIf EndWith Return m.llOk EndProc Durch das Anchoring von VFP 9.0 werden noch ein paar Positionierungen korrigiert. Somit braucht man sich zur Designzeit nicht um solche Kleinigkeiten kümmern. Über die Eigenschaft This.lCustomTitlebar haben wir die oben bereits genannte Flexibilität, dass wir die Titlebar auch an beliebiger Stelle auf dem Form platzieren können, etwa wenn man einen Offset nach rechts oder unten braucht. Gut, der bisherige Code war erst die Pflicht, kommen wir nun zur Kür und zum eigentlich Ziel des Artikels; der Erstellung von semitransparenten Formularen. GDIPlusX aus VFPX hilft für die optische Darstellung Da die bisherigen Ansätze über die Win32 API nicht zum gewünschten Ergebnis geführt haben, verfolgen wir nun einen anderen Ansatz. Unsere Container-Klasse ist bereits vollständig transparent und durch die Verwendung von PNG-Grafiken (Portable Network Graphics) mit Alpha-Kanal als Hintergrund können wir nun den gewünschten Opacity-Effekt produzieren. Und das sogar in beliebigen Shapes und Farbennuancen. Hier zunächst einmal das zu erreichende Ziel als Grafik: Transparente Container-Form mit semitransparenter Grafik Hier kommt ausschliesslich eine Hintergrundgrafik zum Einsatz, die zur Laufzeit mittels GDI+ ad hoc generiert und dargestellt wird. Der Rahmen wird durch Image.BorderStyle = 1 erzeugt. Wie man sehr gut sehen, besteht die Hintergrundgrafik aus zwei Bereichen - semitransparente Titlebar und nichttransparenter Hintergrund. Dies wird in einem Zug in der Methode CreateBackground des Containers erzeugt. Procedure This.CreateBackground *=================================================== * Erstellen der 'Background'-Grafik im Fluge... mittels GDIPlusX aus VFPX * GDIPlusX wird im Hauptprogramm bereits aktiviert. * _SCREEN.AddProperty(\"System\", ; * NEWOBJECT(\"xfcSystem\", LOCFILE(\"system.vcx\",\"vcx\"))) *=================================================== With This Local llOk m.llOk = .T. If m.llOk m.llOk = DoDefault() EndIf If m.llOk With _Screen.System.Drawing * Create an empty bitmap Local loBitmap As xfcBitmap, ; lnLeft, lnTop, lnWidth, lnHeight, ; lnOffset, laPoints[1] Dimension laPoints[8] m.lnWidth = This.Width m.lnHeight = This.Height m.lnOffset = 2 loBitmap = .Bitmap.New(m.lnWidth, m.lnHeight) * Initialize the graphics object Local loGfx As xfcGraphics loGfx = .Graphics.FromImage(loBitmap) * Make all image transparent loGfx.Clear(.Color.FromARGB(0,0,0,0)) * Draw the 'background' polygon w/ rounded corners. m.lnLeft = 10 m.lnTop = 35 m.laPoints[1] = .Point.New(m.lnLeft + m.lnOffset, m.lnTop) m.laPoints[2] = .Point.New(m.lnWidth - m.lnLeft - m.lnOffset, m.lnTop) m.laPoints[3] = .Point.New(m.lnWidth - m.lnLeft, m.lnTop + m.lnOffset) m.laPoints[4] = .Point.New(m.lnWidth - m.lnLeft, m.lnHeight - m.lnOffset - m.lnLeft) m.laPoints[5] = .Point.New(m.lnWidth - m.lnLeft - m.lnOffset, m.lnHeight - m.lnLeft) m.laPoints[6] = .Point.New(m.lnLeft + m.lnOffset, m.lnHeight - m.lnLeft) m.laPoints[7] = .Point.New(m.lnLeft, m.lnHeight - m.lnOffset - m.lnLeft) m.laPoints[8] = .Point.New(m.lnLeft, m.lnTop + m.lnOffset) loGfx.FillPolygon(.SolidBrush.New(.Color.FromARGB( ; Int(255 * This.BackOpacity / 100),This.BackColor)), ; @laPoints) * Draw the 'titlebar' polygon w/ rounded corners. m.lnLeft = This.lblCaption.Left - 3 m.lnTop = 1 m.lnHeight = 25 m.laPoints[1] = .Point.New(m.lnLeft, m.lnTop) m.laPoints[2] = .Point.New(m.lnWidth - m.lnOffset - m.lnTop, m.lnTop) m.laPoints[3] = .Point.New(m.lnWidth - m.lnTop, m.lnTop + m.lnOffset) m.laPoints[4] = .Point.New(m.lnWidth - m.lnTop, m.lnHeight - m.lnOffset) m.laPoints[5] = .Point.New(m.lnWidth - m.lnOffset - m.lnTop, m.lnHeight) m.laPoints[6] = .Point.New(m.lnLeft, m.lnHeight) m.laPoints[7] = .Point.New(m.lnLeft - m.lnOffset, m.lnHeight - m.lnOffset) m.laPoints[8] = .Point.New(m.lnLeft - m.lnOffset, m.lnTop + m.lnOffset) loGfx.FillPolygon(.SolidBrush.New(.Color.FromARGB( ; Int(255 * This.ForeOpacity / 100),This.ForeColor)), ; @laPoints) * Save as PNG to keep transparencies Local lcFilename, loPng m.lcFilename = Addbs(GetEnv(\"APPDATA\")) + This.Name + \".png\" loBitmap.Save(m.lcFilename, .Imaging.ImageFormat.Png) EndWith .AddObject(\"Background\", \"Image\") .Background.Move( ; 0, 0, ; .Width, .Height ; ) .Background.BorderStyle = 1 .Background.Zorder(1) .Background.Picture = m.lcFilename .Background.Anchor = 15 .Background.Visible = .T. EndIf EndWith EndProc Die Kommentare im Quellcode erklären die Vorgehensweise ausreichend. Dennoch möchte ich hier kleinere Anmerkungen zum Code geben. Die Farbgebung der beiden grafischen Elemente - Titlebar und Background - erfolgt durch die Verwendung der Container-Eigenschaften ForeColor (für Titlebar) und BackColor (für Background). Da der Container eh vollständig transparent ist, kann man die Properties bedenkenlos zweckentfremden. Zumindest spart man sich hierdurch eigene Namen und kann den Color Picker im PropertySheet verwenden. Zusätzlich habe ich entsprechende Eigenschaften für die Opacity - ForeOpacity und BackOpacity - in Prozent angelegt. Somit können wir durch Setzen von vier Eigenschaften am Container den gewünschten Effekt unserer Semitransparenz in beliebigen Farben regulieren. Beliebige Variationen für Farbgebung und Opacity sind über Eigenschaften möglich Mit einer wenig Umstellung im Code könnte man auch die nachträgliche Veränderung der Hintergrundgrafik durch erneute Generierung realisieren. Ob das sinnvoll ist, sei dem Entwickler bzw. dem Anwender selbst überlassen. Verwaltung der 'Forms' an zentraler Stelle Da es sich bei unseren 'Formularen' ja um Container handelt, benötigen wir auch einen Mechanismus zum Laden der Instanzen. Das größte Problem, dass es in Verbindung mit VFP-Containern zu lösen gilt, ist das Fehlen der freien Instanzierung wie es bei Forms und Formklassen möglich ist. Container können in VFP nur aggregiert werden, damit sie sichtbar werden. Bei einer zweiten Betrachtung scheint die Lösung dennoch recht einfach zu sein, da wird bereits ein globales Form immer zur Verfügung haben: _Screen Schaut man sich übrigens die Vorgehensweise bei VFP-Forms an, erkennt man auch sehr schnell, dass diese ebenfalls über den _Screen verwaltet werden: _Screen-Forms ist ein Array mit sämtlichen Referenzen der Formobjekte. Und durch Nachbildung dieser Funktionalität erreichen wir das gleiche Resultat. Wir aggregieren unsere Container ebenfalls an das _Screen-Form(set) und legen die Verwaltung in einer Collection (anstelle eines Arrays) ab. Dadurch werden die Containerinstanzen an eine globale Stelle aggregiert und stehen in der gesamten Anwendung jederzeit zur Verfügung. Durch Vergabe von dynamischen Namen können wir ebenfalls stressfrei multiple Instanzen der gleichen Containerklasse erzielen und nutzen. Dazu erstellen wir uns einen generischen Container-Loader, der diese Aufgabe übernimmt. Die Loaderklasse erstellen wir übrigens in einem PRG namens container.prg. \uD83E\uDD2A Auf diese Weise können wir unsere Containerobjekte über zwei gleichwertige Wege erzeugen: `` In Analogie zum Aufruf von VFP-Formularen mittels DO Form <SCX> WITH <Parameter> bietet unser Programm eine vergleichbare Funktionalität gemäß folgender Syntax: DO Container With <Klasse>, <Bibliothek>, <Parameter> Auf Grund der Aggregation des ContainerLoaders an _Screen können wir ebenfalls folgenden Aufruf nutzen: _Screen.Containers.LoadContainer(<Klasse>, <Bibliothek>, <Parameter>) Durch die Verwendung einer Collection für unsere Container anstelle eines Arrays wie bei _Screen.Forms haben wir die Möglichkeit sowohl über einen Index als auch über einen Schlüssel (Namen) auf die Objektreferenz zuzugreifen. Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'semitransparente-forms-erstellen.html'; this.page.identifier = '9271ed1c97_idsemitransparente-forms-erstellen'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "session-notes-fr-vfp-konferenz.html": {
    "href": "session-notes-fr-vfp-konferenz.html",
    "title": "Session-Notes für VFP-Konferenz - Get Blogged by JoKi",
    "summary": "Session-Notes für VFP-Konferenz Copy Markdown View Markdown GitHub Markdown Source Session-Notes für VFP-Konferenz Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'session-notes-fr-vfp-konferenz'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'session-notes-fr-vfp-konferenz'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Session-Notes für VFP-Konferenz') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2006-10-12 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Eigentlich ist Abgabezeit, aber bedingt durch die Projektprogrammierung sitze ich noch an den Session-Notes für die diesjährige Entwicklerkonferenz in Frankfurt. Im Beitrag Visual FoxPro Konferenz Frankfurt 2006 - Geplante Sessions habe ich ja bereits einen Ausblick auf die Themen gegeben: Active FoxPro Pages - was sonst? \uD83D\uDE01 NET4Com - eine Komponente von Sedna CLR Host - ein Experiment zwischen den Welten Derzeit kämpfe ich noch ein wenig mit den Erläuterungen zum CLR Host. Da es sich hierbei um eine Implementierung in multiplen Programmiersprachen (C++, C# und VFP) handelt, gibt's einiges zu formulieren. Dankenswerterweise wurde der Abgabetermin ein paar Tage verschoben, so dass ich die nächsten Abende noch für die Artikel und Slides nutzen kann. Puuuh! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'session-notes-fr-vfp-konferenz.html'; this.page.identifier = '9271ed1c97_idsession-notes-fr-vfp-konferenz'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "share-your-journey-150daysofalc4.html": {
    "href": "share-your-journey-150daysofalc4.html",
    "title": "Share your journey - #150DaysOfALC4 - Get Blogged by JoKi",
    "summary": "Share your journey - #150DaysOfALC4 Copy Markdown View Markdown GitHub Markdown Source Share your journey - #150DaysOfALC4 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'share-your-journey-150daysofalc4'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'share-your-journey-150daysofalc4'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Share your journey - #150DaysOfALC4') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2019-08-23 - Filed under CommunityAndela (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Following my participation as a mentor in the program 3.1 of the Andela Learning Community (ALC) end of last year, I seized the opportunity again to be part of the ALC 4.0 program. As a mentor for the Mobile Web Specialist certification and additionally as a learner for the Google Cloud track. Why participate? The challenge, or better said the chance, came at the right time for me. After I passed a few of the role-based certifications for Microsoft Azure, I had an increasing curiousity to check out other cloud options. Being an organiser of GDG Mauritius I thought that having a look at the Google Cloud Platform (GCP) might be the best fit. Plus, the ability to exchange and network with other like-minded and interested participants from all over Africa seemed to be too good to ignore. Those are the main reasons I signed up for the ALC 4.0 program. Access to a sub-set of the Pluralsight course library or the chance to be selected for an exam voucher for the Google certification were never a criteria to take part in this challenge. Since years I'm an active subscriber of Pluralsight, and the investment in my personal growth regarding the Google exam(s) is it worth any way. Application phase At the end of April, I received an email from Andela that they are launching the [ALC 4.0] Google Africa Certification Scholarship Opportunity. The program will deliver training to 30,000 more learners across 15+ countries in Africa and will run for a period of 6 months starting from 15th May 2019. Participants are given an offer to Grow with Google and to receive education from Pluralsight supported by the Andela Learning Community. And after a quick consultation with my calendar and work schedule I submitted my application to the Google Africa Certification Scholarship program a week or two later. The program advertised the following for successful participants: Learners who make it through the four sessions may be eligible for an opportunity to take the MWS, AAD, or ACE exam at no cost, with the certification exam fee paid for by Google. For reference: MWS - Mobile Web Specialist AAD - Associate Android Developer ACE - Associate Cloud Engineer Again, for me it was the perfect opportunity to explore and probably go deeper into the bells and whistles of GCP. Having some pressure to stay focused on the task helps. And why not? Probably get certified as Associate Cloud Engineer and going further for more Google certifications. So, let's do this... Additionally, I applied as a mentor to allow learners to effectively participate and apply their learning. Sharing some knowledge isn't a bad idea after all. BTW, all information about this program, Grow with Google and Andela [ALC 4.0], can be found here and here. Selection phase Right from the start the pressure to continue in the program was present: [...] we have received a large number of applications whereas we will only be selecting 30,000 learners. The onboarding is quite clear about what is expected from participants. Be active, be present and enjoy the journey as much as possible. Fulfilling the criteria of the selection phase took merely a few days. The content provided by Pluralsight is highly interesting and actually not directly related to the track I applied for. There are really good video courses that I wasn't even aware of that they existed on the learning platform. Clearly, a huge win situation already then. When I got the email, I thought that 30,000 seats would be too small for such a huge region. And during the ALC 4.0 Scholars Onboarding Call (see 6:32 min mark) it was said that a whooping 121,700 unique applications have been received. Whew! Learning Phase 1 The selection phase ran till end of May, and by mid of June I received the confirmation that I've been selected for Phase 1 of the ALC 4.0 program. Selected participants got an invitation to a dedicated Slack workspace in order to keep informed about the latest news, updates, and activities in the program. Overall a very good idea. Although at certain times the flood of posts, questions and comments felt a bit overwhelming. Certain information, links and common answers, had to be given over and over again despite the fact that it had been clearly communicated by the team of ALC - either per email or announcement in the #general channel(s) on Slack. During the ALC 4.0 Global Meeting, fellow learner A-J Roos did a great job in regards to tips and tricks in the Mobile Web Specialist track. You might like to read more about it here: Andela ALC 4.0 Global Meetup - My Thoughts. Although I have been asked to do similar for the Cloud track, I couldn't seize this opportunity due to other priorities that had been planned since a while on that day. Finally, phase 1 picked up some momentum. Apart from the broad advancement criteria of the program to pass into the next phase the team at ALC came up with two additional challenges: 5 Days of Code Challenge The challenges were about practicing real-world tasks in the GCP using the Qwiklabs environment with sponsored student accounts. During the first iteration of the #5DaysofCodeChallenge participants had to run 15 hands-on-lab courses, and another six labs during the second run. For each successfully completed lab we had to take screenshots of the confirmation emails and share them in a public folder on Drive or Dropbox. You can find mine here. ALC 4 Cloud: Challenge 1.0 ALC 4 Cloud: Challenge 2.0 That was good fun, at least for me. Given that access to all Qwiklabs as part of the video modules that had been available since the very beginning of phase 1, I completed some of them already ahead of the challenge announcements. Oh, and surprisingly both challenges had been extended by a few days. Having had those two challenges with their tight schedule seemed to have separated the amount of participants. Surely, there were the ones that completed all labs and submitted their screenshots within the given deadlines. Then, there were the ones struggling with technical issues on the Qwiklabs platform or with environmental constraints at their location, i.e. power outage, lack of access to stable internet connectivity, only temporary access to a computer, etc. And last but not least, the passive ones who might have lost interest to continue. You can view a walkthrough of a Qwiklabs lab in the ALC Global Meetup 2.0. It's nicely explained and demoed by fellow learner Oyewole. Either way, phase 1 ended on a positive note for me. I managed to watch all video courses in the Main Track and the Deep Dive. I worked through almost all of the 26 Qwiklabs, some of them even multiple times. And I was looking forward to the outcome of the election process. Learning Phase 2 Watching learning videos on Pluralsight became an almost daily morning routine, and completing the two challenges regarding GCP was good fun, too. I even managed to tweet (ir-)regularly about my progress during that time. The official end of phase 1 came... and by mid of August the following appeared in my inbox: Wow! It feels great to stay in the program and to be able to continue this amazing journey with approximately 10,000 other learners from Africa. Earlier this week, I read that MWS learner A-J Roos also progressed to phase 2. Congrats on that. He penned down another great article on his blog: Being selected for Google Africa Certification Scholarship Phase 2. The communicated time frame of phase 2 has a larger period than the previous one: Over the next 4 months we will support you on your journey to completing this program. Actually, I'm a bit concerned with this. Four months is quite some time and keeping the motivation high for learners, including myself, might be a tough challenge for program managers and program assistants at ALC. Time will tell... Pluralsight assimilated tons of video material for phase 2, and I really mean it. Again, there are two channels available - namely Main Track and Deep Dive - which provide us with approximately 10 hours and a whooping 105 hours of video modules. That should do for 4 months. More interesting, what started with a possible certification as Associate Cloud Engineer as main target has now been extended with course material covering Professional Cloud Architect and Professional Data Engineer. Given the additional content I'm really looking forward to get deeper into GCP. And how the numerous services and features directly compare to the ones in Microsoft Azure. Rest assured there are going to be more articles soon. And last but not least, we have been presented with the phase 2 timeline of the ALC 4.0 program: Phase 2 (11,000 learners) (18 Aug - 14 Oct) Project (4,000 learners) (23 Oct - 9 Nov) Certification (1,000 learners) (9 Nov - 31 Nov) That looks pretty neat, and there are supposedly meetings planned every two weeks. A little less than two months to watch roughly 115 hours of videos and working through a number of Qwiklabs is reasonable in my opinion. Learning Phase 2 - Project With close connections to software development and actually writing source code in both tracks, Mobile Web Specialist and Android, I understand that the pinnacle of the ALC 4.0 program might be an interesting capstone project. However I am really wondering how this is supposed to work in the Cloud track. Perhaps doing the planning and architecture of an infrastructure deployment allowing a smooth, resilient and elastic operation of a specific requirement could be a possible scenario to realise. Probably with some implementation of Cloud Functions or microservices on Google Kubernetes Engine (GKE). That's my speculation at the moment. I'd be glad to see a capstone project. Let's wait and see... Update: It seems that the Cloud \"project\" is working with even more Qwiklabs. Okay, that should work well for me. The ALC 4.0 program on Youtube The following is more of a reference section for myself. Nonetheless, the video recordings of the Andela Learning Community on Youtube are informative about what we are actually doing. ALC 4.0 : Ask Me Anything Session 1 ALC 4.0 Scholars Onboarding Call ALC 4.0 Global Meetup ALC Global Meetup 2.0 ALC 4.0 Expert Session ALC 4.0 Phase I Grand Finale Event Phase II: Google Africa Developer Scholarship Learner Onboarding Google Africa Developer Scholarship Learning Phase II Onboarding (V2) Note: More videos to be added as they happen... Some random thoughts... If you made this far. Thanks for your interest and your time spent reading this article. Following are some aspects that caught my attention since joining the ALC 4.0 program. If you would like to know more or in case you would like to share some of your experience in regards to GCP, ALC 4.0 or maybe Slack kindly write your feedback in the comment section below. Number of participants With the announcement of phase 2 I had a look at the figures on Pluralsight as well as the #general channels on Slack, and came up with the following observation: AFAIK, phase 1 had ~ 33,000 learners in total across all three tracks. Which would be roughly 10k +/- per track. If you check the #general channel you'd see there are hardly 6k learners that joined the Slack workspace, and then < 4k on Pluralsight. With a rough goal of around 10-12k learners in phase 2, this would mean that as good as every active learner of Cloud phase 1 literally passed into the next stage... At the end of phase 2 there's another selection process for approx. 4-5k in total (or ~1.3k per track) for the project phase. Seeing the current number of followers on the Phase 2 learning paths.. Well, this might have a similar outcome in a few months. Just running the numbers, and having wild guesses... What's your opinion here? At the time of writing there were less than 4,000 participants in the #general channel on the ALC 4 Phase 2 Slack workspace. This channel has been created for all 3 tracks. Now, after a few days in it's way below the projected 10,000 learners. Additionally, see the number of followers in the Learning Phase 2 channels on Pluralsight per track: ~1,500 for Cloud Engineer ~1,300 for Associate Android Developer ~1,800 for Mobile Web Specialist That's not even half the amount in total, and some participants are subscribed to more than one track. Update: During the recent Google Africa Developer Scholarship Learner Onboarding call the actual numbers of phase 2 have been revealed. More than previously expected. With that extensive amount of time plus the reduced number of participants in phase 2 I'm wondering whether there is going to be the initially mentioned capstone project at the end of phase 2. Video content and lab instructions Cloud technology and services are in a constant movement. Whether it is AWS, Microsoft Azure, or Google Cloud Platform doesn't make any difference after all. Since the recording of the video modules there had been updates and modifications in GCP's Cloud Console. Most remarkable is the absence of the Cloud Launcher in the sidebar navigation menu. It is called Marketplace nowadays. I also noted that the course material by Google has been published on the Coursera platform previously. The recordings on Pluralsight however have been re-branded with an introduction at the beginning as well as a summary at the end of each module. And the shortcut URLs to the Qwiklabs have been added in-between. Speaking of Qwiklabs, in general the instructions of each lab are precise and provide sufficient information and guidance to successfully complete the lab. However, there had been a few cases where the described steps are not matching the UI of the Google Console anymore. It's not as bad as the feared magical tutorials but certain features are at a different location or have been renamed. Once you figure that out it's possible to finish the lab. I left my feedback with Qwiklabs, and hopefully those glitches will be corrected in the coming reviews. And there are the video recordings of the Andela Learning Community on Youtube. Environmental obstacles The number of posts where participants reported about issues in regards to their environment was unfortunately very impressive. The problems ranged from electricity outages (sometimes even over several days) and therefore no connection to the internet, over weak stability and high cost of data connectivity (which impacted/interrupted the execution of Qwiklabs), to all sorts of technical observations. Touching wood, despite living on a remote island I didn't have to worry about such commodities after all. The power grid work absolutely fine, and it's been awhile since we had serious issues regarding electricity. Our ADSL fixed line works smoothly, and there had been no interruptions on the sea cables recently. And compared to other African nations it seems that the prices for data packages are more affordable, too. Quality of communication in the Slack channels In general, it is a pleasure to follow the (endless) chats in the Slack workspace(s). They are both interesting and informative. I managed to provide assistance to a number of topics. Most certainly, there are topics which get a bit more interest, and that there are several people probably asking for same or similar content. However, the inability (or perhaps laziness) to read a few lines up in the channel to check whether an issue has been discussed already is kind of mindboggling to me. There had been cases where exactly the same issue has been posted by different people in consecutive sequence in shortest period of time. Crossing fingers this is going to improve with fewer people in phase 2. Curiousity among peers To close this article I would like to emphasise that there is genuine interest among peers to learn from each other. Recently, I was asked four questions about my professional background and opinion about software engineering. It's really good that this is happening, as we can learn from each other at any time. Amazing collection of resources During phase 1 we had a Slack channel specifically for additional resources and further reading on GCP. This is an invaluable trove of treasures with literally hundreds of interesting links. Unfortunately, Andela runs Slack in the free tier which has a limit of 10,000 active posts. Meaning, a solid number of posts is now inaccessible. Another scholar and Learning Community Ambassador (LCA), using the alias Banky, created an online repository on GitHub: My ALC 4.0 Handbook which covers all kind of links to the video channels on Pluralsight. Highly recommended! Note: Pluralsight officially published the channels and courses of the ALC 4.0 program here: What courses do I have access to with the ALC 4.0 program? Maybe it motivates you to follow this journey on your own merits. Image courtesy: Andela Learning Community if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'share-your-journey-150daysofalc4.html'; this.page.identifier = '9271ed1c97_idshare-your-journey-150daysofalc4'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "sharepoint-2013-developer-ramp-up-part-1.html": {
    "href": "sharepoint-2013-developer-ramp-up-part-1.html",
    "title": "SharePoint 2013 Developer Ramp-Up - Part 1 - Get Blogged by JoKi",
    "summary": "SharePoint 2013 Developer Ramp-Up - Part 1 Copy Markdown View Markdown GitHub Markdown Source SharePoint 2013 Developer Ramp-Up - Part 1 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sharepoint-2013-developer-ramp-up-part-1'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sharepoint-2013-developer-ramp-up-part-1'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'SharePoint 2013 Developer Ramp-Up - Part 1') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-09-02 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Today I had a little spare time during the morning hours and I decided that after checking MVA that I'm going to query the available course material over at Pluralsight. Wow, thanks to fantastic corporations and acquisitions there are lots of courses available. Nicely split by SharePoint version as well as particular interest group. Additionally, I found a couple of online blogs and community sites that I'm going to visit regularly during the next couple of weeks. Today's resource(s) Of course, I'm \"all in\" for the latest developer resources: SharePoint 2013 Developer Ramp-Up - Part 1 - Understanding the Platform and Developer Experience SharePoint 2013 Developer Ramp-Up - Part 2 SharePoint 2013 Developer Ramp-Up - Part 3 SharePoint 2013 Developer Ramp-Up - Part 4 SharePoint 2013 Developer Ramp-Up - Part 5 SharePoint 2013 Developer Ramp-Up - Part 6 I guess, I'm going to stick to the Pluralsight library until the end of this week. We'll see... Anyway, apart from the video material I came across a couple of other websites which I'd like to list here, too. That's mainly for personal reference instead of bookmarking in the browser, I'll use my own blog for that purpose. Atkinson's SharePoint Blog Düsseldorfer Jung Doerflers SharePoint Blog SharePoint Community Absolute SharePoint The links are in no preferential order and I added them as soon as I found them. Most probably, I'm going to report about specific articles from those resources during this challenge. So, stay tuned and I try to provide more details on certain topics. Takeaway First contact with the 'real stuff' in order to get an idea about software development in Microsoft SharePoint and beyond. Unfortunately and as already expected, the marketing department over at Microsoft seemed to have nothing better to do than to invent new names and baptise literally the same product with every release. Luckily, the release cycles between versions have been three years (roughly) - 2007, 2010, and 2013. Nonetheless, there will be a lot of version-specfic issues to tackle during this learning phase. Especially, when it's about historical expressions like 'WSS'* like I had it yesterday... It's going to be exciting and demanding to catch up with roughly 6-7 years of development and changes. Okay, let's face it. WSS stands for Windows SharePoint Services 3.0 which forms the 'core engine' of SharePoint 2007. Part 1 of Andrew Connell's series on SharePoint 2013 for developers provides a brief history and overview of the various product names and their relation to the actual SharePoint version. I guess, I might create a cheat-sheet or something comparable in order to reduce the level of confusion while reading through other material: SharePoint 2007 (aka SharePoint v3 aka SharePoint 12) Windows SharePoint Services (WSS) 3.0 Microsoft Office SharePoint Server (MOSS) 2007 .NET Framework 3.0, 32-bit or 64-bit OS SharePoint 2010 (aka SharePoint v4 aka SharePoint 14) Microsoft SharePoint Foundation (SPF) 2010 Microsoft SharePoint Server (SPS) 2010 .NET Framework 3.5 SP1, 64-bit OS only SharePoint 2013 Microsoft SharePoint Foundation (SPF) 2013 Microsoft SharePoint Server (SPS) 2013 .NET Framework 4.5, 64-bit OS only After this quick excursion it is getting more interesting. SharePoint 2013 has a number of Development Practices and Techniques under the hood, and it will be quite a decision process depending on the task requirements to choose the correct path to go. At the moment, the following two options seem to be my future fields of operation: Client-Side Object Model (CSOM) REST API and OData syntax As part of my job assignment, I see myself developing within Visual Studio 2012/2013. Most probably the client development in C# will be using CSOM but of course I'll keep an eye on the REST API, too. JavaScript has quite a momentum since a while and it would a shame to ignore this type of opportunity and possibilities. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'sharepoint-2013-developer-ramp-up-part-1.html'; this.page.identifier = '9271ed1c97_idsharepoint-2013-developer-ramp-up-part-1'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "sharepoint-2013-developer-ramp-up-part-2.html": {
    "href": "sharepoint-2013-developer-ramp-up-part-2.html",
    "title": "SharePoint 2013 Developer Ramp-Up - Part 2 - Get Blogged by JoKi",
    "summary": "SharePoint 2013 Developer Ramp-Up - Part 2 Copy Markdown View Markdown GitHub Markdown Source SharePoint 2013 Developer Ramp-Up - Part 2 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sharepoint-2013-developer-ramp-up-part-2'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sharepoint-2013-developer-ramp-up-part-2'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'SharePoint 2013 Developer Ramp-Up - Part 2') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-09-03 - Filed under Development (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); As stated already yesterday, today I continued with the available course material on Pluralsight. For sure interesting topics in the second part of the series but not the field of operation I'm going to work in later. During the course you get a lot of information about how to create and deploy SharePoint Solutions and hosted SharePoint Apps. Today's resource(s) Apart from some blog articles I watched in the following course today: SharePoint 2013 Developer Ramp-Up - Part 2 - Developing SharePoint Solutions and Apps Not thrilling but still two solid hours to go. Takeaway One of the coolest aspects I figured out today is that SharePoint development can be done easily in JavaScript and C# - just as you like or prefer. It's actually pretty cool to see that you could integrate external JS libraries like datajs, knockout,js and so forth in order to implement your solution. And that you should be very familiar with Microsoft PowerShell. Not only to simplify some repetitive work but also to do be able to get things going in SharePoint. Having a decent background knowledge in Linux, I find this pretty amusing and remember the initial baby steps when PowerShell was introduced some years back (Note: German language). The outcry as well as the hype was too funny. Honestly, I have kind of mixed feelings about today's progress. Surely, there was interesting information about developing extensions directly for and in SharePoint... Hm, I'll leave that one for now and probably it might be helpful someday. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'sharepoint-2013-developer-ramp-up-part-2.html'; this.page.identifier = '9271ed1c97_idsharepoint-2013-developer-ramp-up-part-2'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "side-load-and-read-your-ebooks-on-kindle-for-android.html": {
    "href": "side-load-and-read-your-ebooks-on-kindle-for-android.html",
    "title": "Side-load and read your ebooks on Kindle for Android - Get Blogged by JoKi",
    "summary": "Side-load and read your ebooks on Kindle for Android Copy Markdown View Markdown GitHub Markdown Source Side-load and read your ebooks on Kindle for Android Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'side-load-and-read-your-ebooks-on-kindle-for-android'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'side-load-and-read-your-ebooks-on-kindle-for-android'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Side-load and read your ebooks on Kindle for Android') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-04-25 - Filed under Android (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Although, I'm using the freely available Kindle app for Android it only happened recently that I run into the situation to upload an existing ebook in mobi format to my Android tablet. To be more precise, I purchased some titles from O'Reilly's Ebook library. Upload the ebook to your device Well, uploading the file wasn't really the problem. There are multiple apps in the Play Store which either allow you to access your existing network resources, like an SMB-based file share, FTP server or NAS, or turn your device into a server which can be accessed from your desktop machine via network. Just for matters of completion, I'm using ES File Explorer to access my NAS or run Air Droid to directly access my tablet through a browser. Anyway, there are tons of other apps to provide similar features. Maybe you leave a comment on your recommendations. Where to store the ebook? So, while transferring the ebook to the device isn't really a challenge I started to wonder where to store the file actually? Does the Kindle app scan your directories and possibly include any ebook to its library found? Similar to the Gallery app. Initially, I was hoping for that as I put the file into my regular 'Downloads' folder. Firing up and sync'ing the Kindle app did not produce the expected book cover in my library. Of course, you might think... Okay, second try: Using my favourite file explorer I browsed through the folder hierarchy on the device. This revealed that there are two potential candidates to store the file: /sdcard/kindle and /sdcard/data/Android/com.amazon.kindle/files Solution: /sdcard/kindle Actually, it turns out that the first one is the right one (read: better one) even though both seem to work. Simply copy your mobi-based ebook into that directory and then fire up your Kindle app for Android. Unfortunately, any side-loaded ebook is not synchronised with your Amazon Cloud storage and therefore will not appear on any of the other devices you might use to access your Kindle library. In my case, that's not a big deal mainly because I only use my tablet to read ebooks after all. Alternative: Send an email to your Kindle account Frankly speaking, I haven't tried this approach (yet). The concept here is that Amazon provides a service called 'send to kindle' which gives you an unique email address for your Kindle account and allows you to send any kind of documents (mobi, doc, pdf, etc) to yourself and it will be distributed to your Kindle devices and readers, like ie. Kindle app for Android. Guess, I should give it a try and report soon about the experience. Update: I just did a test run and sent a freely available ebook from Project Gutenberg as an email attachment to one of my Kindle 'devices'. After a 'Sync & Check for Items' the new title appeared as expected in my library and was downloaded to the device. Unfortunately, it doesn't appear in the cloud library and therefore not on other devices or Kindle reader apps like on iOS or Windows 8. Sorry, my mistake. Eventually, I was too fast last time checking. Documents sent via email to your Amazon Cloud storage are available on other Kindle devices and readers. At least, I am able to access them on Kindle for iOS on my iPad and the other Android devices. As for the Kindle app on Windows 8 it remains a problem to access those documents. If you are more likely in the Apple universe, then please read on: Side-load and read your mobi ebooks on Kindle for iOS if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'side-load-and-read-your-ebooks-on-kindle-for-android.html'; this.page.identifier = '9271ed1c97_idside-load-and-read-your-ebooks-on-kindle-for-android'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "side-load-and-read-your-mobi-ebooks-on-kindle-for-ios.html": {
    "href": "side-load-and-read-your-mobi-ebooks-on-kindle-for-ios.html",
    "title": "Side-load and read your mobi ebooks on Kindle for iOS - Get Blogged by JoKi",
    "summary": "Side-load and read your mobi ebooks on Kindle for iOS Copy Markdown View Markdown GitHub Markdown Source Side-load and read your mobi ebooks on Kindle for iOS Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'side-load-and-read-your-mobi-ebooks-on-kindle-for-ios'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'side-load-and-read-your-mobi-ebooks-on-kindle-for-ios'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Side-load and read your mobi ebooks on Kindle for iOS') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2013-05-01 - Filed under iOS (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Inspired by my own article on side-loading ebooks on Kindle for Android I wanted to know how to do it for iOS-based devices like the iPhone, the iPad, or the iPod. It seems that the possibilities are on the one side different but on the other side somehow 'richer' compared to Android. First of all, due to the 'walled garden' concept in iOS you can not simply hook up your device to your Mac or PC and transfer the files 'as-is'. This is the major difference to Android which is a huge drawback according to my perception but not too bad after all for the majority of users in general. Furthermore, the various methods to side-load an ebook to your Kindle app have been evolved. Mainly because several hacks from the old days became obsolete due to updates on the app itself. This is pretty positive that the app developers over at Amazon are taking the feedback of their users into consideration and enable more features over the time. Using iExplorer (previously known as iPhoneExplorer) Several search results are pointing the forum threads what recommend to use a software called iExplorer in order to transfer your non-Amazon ebooks to your device. The important information in this scenario seems that the folder names vary between running iExplorer on a Mac or using it on a Windows PC: Mac folder: Apps/Kindle/Documents/eBooks PC folder: Apps/com.amazon.Lassen/Documents/eBooks Either way your book is going to show-up in your library. Most probably without a cover image. But iExplorer seems to require the installation of iTunes which brings us to the situation that this method about iPhoneExplorer or nowadays iExplorer is actually obsolete. So, in case that you are only interested to side-load your Kindle titles to your iPhone, iPad or iPod don't bother yourself with the application. Otherwise, it's a great piece of software to control other parts of your devices, too. Using iTunes This might be the easiest version after all. Connect your device via USB, navigate within iTunes to Apps > File Sharing > Apps: Kindle > Kindle Documents and simply drag & drop your MOBIs from Mac Finder or Windows Explorer there. Or choose your files via the Add... dialog. Transfer your .mobi files to your Kindle for iPad with iTunes Of course, always assuming that you are actually having a Mac or Windows system at your fingertips. This looks completely different while running on Linux. 'Downloading' the ebook to your device Since Kindle version 2.5 (~ November 2011) it is possible to use the registered app features directly on your device. Access millions of free and out-of-copyright books from Project Gutenberg, Internet Archive, and other online sources. Open supported files from Safari and Mail or use File Sharing in iTunes to transfer files to your Kindle app. Sounds too good to be true? While accessing .mobi files Safari offers a dialog to 'Open in Kindle' No, it actually works pretty smart but as stated in the release notes it is somehow restricted to the native iOS apps like Safari or Mail. Unfortunately, I couldn't manage to download a file in mobi format from the Project Gutenberg web site in Chrome: Chrome fails to open .mobi files for Kindle for iPad \"Download Failed - Chrome cannot download this file. Error 102 (): Unknown File Type.\" - Thanks for that! Amazon's Send to Kindle feature As already mentioned in the Android experience your Kindle for iPad app or your Kindle for iPhone/iPod app has the ability to receive documents (in)directly through your Amazon's Cloud storage. Either you change to the tab 'Docs' (Kindle for iPad, might be different on iPhone/iPod) or you open the Settings menu in your Kindle app to discover your device-specific 'Send-to-Kindle Email Address'. Lookup your 'Send-to-Kindle' Email Address in the Settings menu Next, send an email to that particular mail address with your MOBI file or any other kind of document as attachment. Wait for some minutes and synchronise your device. All described methods are very simple to follow and sideloading your own Kindle ebooks on your Kindle for iPad app isn't really an issue anymore as it used to be in the past. Thanks to standard features in iTunes and the 'Send-to'Kindle' mail delivery you can quickly enjoy your books on any iOS-based device. Happy reading! if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'side-load-and-read-your-mobi-ebooks-on-kindle-for-ios.html'; this.page.identifier = '9271ed1c97_idside-load-and-read-your-mobi-ebooks-on-kindle-for-ios'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "sie-sind-da.html": {
    "href": "sie-sind-da.html",
    "title": "Sie sind da... - Get Blogged by JoKi",
    "summary": "Sie sind da... Copy Markdown View Markdown GitHub Markdown Source Sie sind da... Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sie-sind-da'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'sie-sind-da'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Sie sind da...') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-11-25 - Filed under Community (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); \"Ja, wo laufen sie denn?\", \"Mein Gott, warum machen sie das denn?\" - diese netten Zitate von Loriot sind mir spontan bei der deutschen Werbekampagne für Visual Studio 2005, SQL Server 2005 und Biztalk Server 2006 in den Sinn gekommen. Nun, im Vergleich zur amerikanischen Variante \"Ready to Rock the Launch\" wirkt \"Sie sind da\" ein wenig spröde... Aber okay, andere Kultur, andere Sitten, andere Bräuche... Das eigentliche Launchevent wird am 8. und 9. Februar in Karlsruhe stattfinden. Auf der Website https://www.sie-sind-da.de gibt es weiterführende Informationen und die obligatorische Anmeldung. Also, Leute, buchen und dem Spass des Ereignis beiwohnen. Let's get ready to rock the launch, öhem... sie sind da! Bis denne, JoKi if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'sie-sind-da.html'; this.page.identifier = '9271ed1c97_idsie-sind-da'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "skydiving-in-mauritius.html": {
    "href": "skydiving-in-mauritius.html",
    "title": "Yes, I did it - Skydiving in Mauritius - Get Blogged by JoKi",
    "summary": "Yes, I did it - Skydiving in Mauritius Copy Markdown View Markdown GitHub Markdown Source Yes, I did it - Skydiving in Mauritius Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'skydiving-in-mauritius'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'skydiving-in-mauritius'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Yes, I did it - Skydiving in Mauritius') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2010-10-16 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Finally, I did it or better said we did it. Already back in November last year I saw the big billboard advertisement of Skydive Austral Mauritius near Caudan Waterfront in Port Louis and decided for myself that this is going to be the perfect birthday gift for my wife. Simply out of curiosity I would join her tandem jump with a second instructor. Due to her pregnancy of our son I had to be patient... But then finally, her birthday had arrived and on our midnight celebration session I showed her her netbook with the website preloaded. Actually, it was the \"perfect\" timing... Recovery from her cesarean is fine, local weather conditions are gorgious and the children were under surveillance of my mum - spending her annual holidays on the island. So, after late wake-up in the morning, we packed our stuff and off we went. According to Google Maps direction indication we had to drive for roughly 50km (only) but traffic here in Mauritius is always challenging. The dropzone is at the Zone Industrielle Mon Loisir Sugar Estate near Riviere du Rempart at the northern east coast. Anyways, we were not in a hurry and arrived there shortly after noon. The access road to the airfield are just small down-driven paths through sugar cane fields and according to our daughter \"it's bumpy!\". True true true... The facilities at Skydive Austral Mauritius are complete except for food. Enough space for parking, easy handling at the reception and a lot to see for the kids. There's even a big terrace with several sets of tables and chairs, small bar for soft drinks, strictly non-alcoholic. The team over there is all welcoming and warm-hearthy! Having the kids with us was no issue at all. Quite the opposite, our daugther was allowed to discover a lot of things than we adults did. Even visiting the small air plane was on the menu for her. Really great stuff! Image courtesy of Skydive Austral Mauritius - roughly 10.000ft above sea level. While waiting for our turn we enjoyed watching other people getting ready in the jump gear, taking off with the Cessna, and finally coming back down on the tandem parachute. Actually, the different expressions on their faces was one of the best parts while waiting. Great mental preparation as my wife was getting more anxious about her first jump... First, we got some information about the procedures on the plane about how to get seated, tight up with our instructors and how to get ready for the jump off the plane as soon as we arrive the height of 10.000 ft. All well explained and easy to understand after all. Next, we met with our jumpers Chris and Lee aka \"Rasta\" to get dressed and ready for take-off. Those guys are really cool and relaxed for their job. From that point on, the DVD session / recording for my wife's birthday started and we really had a lot of fun... The difference between that small Cessna and a commercial flight with an Airbus or a Boeing is astronomic! The climb up to 10.000 ft took us roughly 25 minutes and we enjoyed the magnificent view over the turquoise lagunes near Poste de Flacq, Lafayette and Isle d'Ambre on the north-east coast. After flying through the clouds we sun-bathed and looked over \"iced-sugar covered\" Mauritius. You might have a look at the picture gallery of Skydive Mauritius for better imagination. The moment of truth, or better said, point of no return came after approximately 25 minutes. The door opens, moving into position on the side on top of the wheel and... out! Back flip and free fall! Slight turns and Wooooohooooo! through the clouds... It so amazing and breath-taking! So undescribable! You have to experience this yourself! Some seconds later the parachute opened and we glided smoothly with some turns and spins back down to the dropzone. The rest of the family could hear and see us soon and the landing was easy going. We never had any doubts or fear about our instructors. They did a great job and we are looking forward to book our next job. I might even consider to follow educational classes on skydiving and earn a license. By the way, feel free to get in touch with Skydive Austral Mauritius. Either via contact details on their website or tweeting a little bit with them. Follow the tweets of Chris and fellows on SkydiveAustral. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'skydiving-in-mauritius.html'; this.page.identifier = '9271ed1c97_idskydiving-in-mauritius'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "small-hiccup-with-vmware-player-after-upgrading-to-ubuntu-1204.html": {
    "href": "small-hiccup-with-vmware-player-after-upgrading-to-ubuntu-1204.html",
    "title": "Small hiccup with VMware Player after upgrading to Ubuntu 12.04 - Get Blogged by JoKi",
    "summary": "Small hiccup with VMware Player after upgrading to Ubuntu 12.04 Copy Markdown View Markdown GitHub Markdown Source Small hiccup with VMware Player after upgrading to Ubuntu 12.04 Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'small-hiccup-with-vmware-player-after-upgrading-to-ubuntu-1204'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'small-hiccup-with-vmware-player-after-upgrading-to-ubuntu-1204'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Small hiccup with VMware Player after upgrading to Ubuntu 12.04') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2012-04-30 - Filed under Linux (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); The upgrade process Finally, it was time to upgrade to a new LTS version of Ubuntu - 12.04 aka Precise Pangolin. I scheduled the weekend for this task and despite the nickname of Mauritius (Cyber Island) it took roughly 6 hours to download nearly 2.400 packages. No problem in general, as I have spare machines to work on, and it was weekend anyway. All went very smooth and only a few packages required manual attention due to local modifications in the configuration. With the new kernel 3.2.0-24 it was necessary to reboot the system and compared to the last upgrade, I got my graphical login as expected. Compilation of VMware Player 4.x fails A quick test on the installed applications, Firefox, Thunderbird, Chromium, Skype, CrossOver, etc. reveils that everything is fine in general. Firing up VMware Player displays the known kernel mod dialog that requires to compile the modules for the newly booted kernel. Usually, this isn't a big issue but this time I was confronted with the situation that vmnet didn't compile as expected (\"Failed to compile module vmnet\"). Luckily, this issue is already well-known, even though with \"Failed to compile module vmmon\" as general reason but nevertheless it was very easy and quick to find the solution to this problem. In VMware Communities there are several forum threads related to this topic and VMware provides the necessary patch file for Workstation 8.0.2 and Player 4.0.2. In case that you are still on Workstation 7.x or Player 3.x there is another patch file available. After download extract the file like so: tar -xzvf vmware802fixlinux320.tar.gz and run the patch script as super-user: sudo ./patch-modules_3.2.0.sh This will alter the existing installation and source files of VMware Player on your machine. As last step, which isn't described in many other resources, you have to restart the vmware service, or for the heart-fainted, just reboot your system: sudo service vmware restart This will load the newly created kernel modules into your userspace, and after that VMware Player will start as usual. Summary Upgrading any derivate of Ubuntu, in my case Xubuntu, is quick and easy done but it might hold some surprises from time to time. Nonetheless, it is absolutely worthy to go for it. Currently, this patch for VMware is the only obstacle I had to face so far and my system feels and looks better than before. Happy upgrade! Resources I used the following links based on Google search results: https://communities.vmware.com/message/1902218#1902218 https://weltall.heliohost.org/wordpress/2012/01/26/vmware-workstation-8-0-2-player-4-0-2-fix-for-linux-kernel-3-2-and-3-3/ Update on VMware Player 4.0.3 Please continue to read on my follow-up article in case that you upgraded either VMware Workstation 8.0.3 or VMware Player 4.0.3. Update on VMware Player 4.0.4 And once again, please read on this article for VMware Player 4.0.4 Update on VMware Player 5.0.0 Please read this article for VMware Player 5.0.0 in Ubuntu 12.10 if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'small-hiccup-with-vmware-player-after-upgrading-to-ubuntu-1204.html'; this.page.identifier = '9271ed1c97_idsmall-hiccup-with-vmware-player-after-upgrading-to-ubuntu-1204'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "so-kann-ich-net-arbeiten.html": {
    "href": "so-kann-ich-net-arbeiten.html",
    "title": "So kann ich net arbeiten! - Get Blogged by JoKi",
    "summary": "So kann ich net arbeiten! Copy Markdown View Markdown GitHub Markdown Source So kann ich net arbeiten! Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'so-kann-ich-net-arbeiten'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'so-kann-ich-net-arbeiten'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'So kann ich net arbeiten!') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2005-02-15 - Filed under General (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Sodele, die Vorbereitungen für den Abend nähern sich dem Ende. Nach ein paar Installationsorgien auf dem Laptop kann ich mir endlich vorstellen, morgen Abend einigermaßen komfortabel arbeiten zu können. Aber der Hammer hat wieder einmal Visual Studio 2003 gebracht. Da werkelt man fröhlich mit einigen Tools in den Tiefen von XML rum und schaut sich auch mal so einiges in VS 2003 an. Schliesslich blickt man ja über den Tellerrand und interessiert immer für die anderen. Nunja, sowas kann auch ernüchternd wirken. So auch diesmal bei dem 'netten' Menüpunkt '' - lasst die Finger davon, wenn ihr eure Optik der XML-Datei erhalten wollt. Irgendwie finde ich das mal wieder funny, denn dieses Feature hat mir schon vor zig Jahren in den ersten Editionen von FrontPage den letzten Nerv geraubt und das Produkt absolut disqualifiziert... Irgendwie hat's Microsoft in den letzten Jahren doch wieder versäumt. Nunja, vielleicht klappt's ja in VS 2005, dass die Priorität auf 'der Entwickler ist Chef im Ring' hinaus läuft und die Formatierung so bleibt, wie ich das haben will und nicht wie es sich irgendjemand im VS Data Entwicklerteam gerade mal so denkt. Aber es gibt ja noch andere Tools - und in den letzten Wochen ist mir das kostenfreie Cooktop absolut ans Herz gewachsen. Richtig klasse, was man mit dem Teilchen so alles anstellen kann. Vor allem inklusive der Möglichkeit XML Transformationen per XSLT ad hoc durchzuführen. Die Results werden sowohl plain als auch in HTML angezeigt. Meiner Ansicht ein Tool, dass auf keiner Entwicklerkiste fehlen sollte. In diesem Sinne, Gute Nacht. PS: Evtl. liegt's auch nur an einer Einstellung; wenn dem so sei, dann ist der eingestellte Defaultwert Murks. Image courtesy of Mmuseums Victoria if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'so-kann-ich-net-arbeiten.html'; this.page.identifier = '9271ed1c97_idso-kann-ich-net-arbeiten'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "social-ecommerce.html": {
    "href": "social-ecommerce.html",
    "title": "Social eCommerce by Stephan Spencer, Jimmy Harding & Jennifer Sheahan - Get Blogged by JoKi",
    "summary": "Social eCommerce by Stephan Spencer, Jimmy Harding & Jennifer Sheahan Copy Markdown View Markdown GitHub Markdown Source Social eCommerce by Stephan Spencer, Jimmy Harding & Jennifer Sheahan Copy Markdown Copy as Prompt Download .MD GitHub Close Loading markdown source... (function () { var rawMarkdownCache = {}; function cleanFrontmatter(text) { if (!text) return ''; // Bulletproof removal of frontmatter: between initial --- and ending --- return text.replace(/^---[\\s\\S]*?\\n---\\s*\\n?/, '').trim() + '\\n'; } function fetchMarkdown(slug, callback) { if (rawMarkdownCache[slug]) { callback(null, rawMarkdownCache[slug]); return; } var rel = ''; var url = rel + 'raw/' + encodeURIComponent(slug) + '.md'; fetch(url) .then(function (res) { if (!res.ok) throw new Error('Markdown source not found: ' + res.status); return res.text(); }) .then(function (text) { var cleaned = cleanFrontmatter(text); rawMarkdownCache[slug] = cleaned; callback(null, cleaned); }) .catch(function (err) { // Fallback: extract plain text from post content var contentEl = document.querySelector('.post-full-content .post-content, .post-content, main .inner'); if (contentEl) { var fallbackMd = '# ' + (document.querySelector('h1') ? document.querySelector('h1').innerText : 'Post') + '\\n\\n' + (contentEl.innerText || contentEl.textContent); rawMarkdownCache[slug] = fallbackMd; callback(null, fallbackMd); } else { callback(err, null); } }); } // Attach Copy Markdown handler var copyButtons = document.querySelectorAll('.btn-copy-markdown, .floating-copy-markdown'); copyButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'social-ecommerce'; var originalHtml = btn.innerHTML; btn.disabled = true; fetchMarkdown(slug, function (err, md) { if (err || !md) { btn.disabled = false; alert('Could not copy Markdown source.'); return; } navigator.clipboard.writeText(md).then(function () { btn.classList.add('copied'); btn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span class=\"btn-text\">Copied!</span>'; setTimeout(function () { btn.classList.remove('copied'); btn.innerHTML = originalHtml; btn.disabled = false; }, 2000); }).catch(function () { btn.disabled = false; }); }); }); }); // Attach View Markdown modal var viewButtons = document.querySelectorAll('.btn-view-markdown'); var modal = document.getElementById('markdown-viewer-modal'); var codeEl = document.getElementById('markdown-viewer-code'); var closeBtn = modal ? modal.querySelector('.markdown-viewer-close') : null; var modalCopyBtn = document.getElementById('markdown-modal-copy-btn'); if (modal && viewButtons.length > 0) { viewButtons.forEach(function (btn) { btn.addEventListener('click', function () { var slug = btn.getAttribute('data-uid') || btn.getAttribute('data-slug') || 'social-ecommerce'; if (codeEl) codeEl.textContent = 'Loading Markdown source...'; if (typeof modal.showModal === 'function') { modal.showModal(); } else { modal.setAttribute('open', ''); } fetchMarkdown(slug, function (err, md) { if (codeEl) { codeEl.textContent = md || 'Failed to load Markdown source.'; if (window.hljs && typeof hljs.highlightElement === 'function') { hljs.highlightElement(codeEl); } // Remove any DocFX code-action / copy buttons that might have been injected var actions = modal.querySelectorAll('.code-action, .code-copy-btn, .action, a[title=\"Copy\"], button[title=\"Copy\"]'); actions.forEach(function (el) { el.remove(); }); } }); }); }); if (closeBtn) { closeBtn.addEventListener('click', function () { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } }); } // Light dismiss on backdrop click modal.addEventListener('click', function (e) { if (e.target === modal) { var rect = modal.getBoundingClientRect(); var isInDialog = ( rect.top <= e.clientY && e.clientY <= rect.top + rect.height && rect.left <= e.clientX && e.clientX <= rect.left + rect.width ); if (!isInDialog) { if (typeof modal.close === 'function') { modal.close(); } else { modal.removeAttribute('open'); } } } }); var modalPromptBtn = document.getElementById('markdown-modal-prompt-btn'); function formatPrompt(markdownText) { var postTitle = document.querySelector('h1.post-full-title') || document.querySelector('h1'); var titleStr = (postTitle ? postTitle.textContent.trim() : 'Social eCommerce by Stephan Spencer, Jimmy Harding & Jennifer Sheahan') || 'Article'; var appUrl = 'https://jochen.kirstaetter.name'.replace(/\\/+$/, ''); var canonicalLink = document.querySelector('link[rel=\"canonical\"]'); var pageUrl = canonicalLink ? (canonicalLink.getAttribute('href') || canonicalLink.href) : ''; if (!pageUrl) { pageUrl = appUrl + window.location.pathname; } if (pageUrl.startsWith('/')) { pageUrl = appUrl + pageUrl; } pageUrl = pageUrl.split('?')[0].split('#')[0]; try { var parsed = new URL(pageUrl, appUrl); if (parsed.hostname === 'localhost' || parsed.hostname === '127.0.0.1' || parsed.hostname.endsWith('.localhost')) { pageUrl = appUrl + parsed.pathname; } } catch (e) {} pageUrl = pageUrl.replace(/\\/index\\.html$/, '/').replace(/\\.html$/, ''); if (pageUrl.length > appUrl.length + 1 && pageUrl.endsWith('/')) { pageUrl = pageUrl.slice(0, -1); } return 'Analyse and use the following article as context:\\n\\n' + 'Title: ' + titleStr + '\\n' + 'URL: ' + pageUrl + '\\n\\n' + '---\\n\\n' + markdownText; } if (modalCopyBtn) { modalCopyBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; navigator.clipboard.writeText(text).then(function () { var origHtml = modalCopyBtn.innerHTML; modalCopyBtn.classList.add('copied'); modalCopyBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalCopyBtn.classList.remove('copied'); modalCopyBtn.innerHTML = origHtml; }, 2000); }); }); } if (modalPromptBtn) { modalPromptBtn.addEventListener('click', function () { var text = codeEl ? (codeEl.innerText || codeEl.textContent) : ''; if (!text) return; var promptText = formatPrompt(text); navigator.clipboard.writeText(promptText).then(function () { var origHtml = modalPromptBtn.innerHTML; modalPromptBtn.classList.add('copied'); modalPromptBtn.innerHTML = '<svg viewBox=\"0 0 24 24\" width=\"14\" height=\"14\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><polyline points=\"20 6 9 17 4 12\"></polyline></svg> <span>Copied!</span>'; setTimeout(function () { modalPromptBtn.classList.remove('copied'); modalPromptBtn.innerHTML = origHtml; }, 2000); }); }); } } })(); 2014-11-13 - Filed under Recension (function() { var el = document.currentScript ? document.currentScript.previousElementSibling.querySelector('.post-full-meta-date') : document.querySelector('.post-full-meta-date'); if (el) { var raw = el.getAttribute('datetime') || el.textContent.trim(); var m = raw.match(/^(\\d{4})-(\\d{1,2})-(\\d{1,2})/); if (m) { var months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; var y = parseInt(m[1], 10); var mo = parseInt(m[2], 10) - 1; var d = parseInt(m[3], 10); if (mo >= 0 && mo < 12) { el.textContent = months[mo] + ' ' + d + ', ' + y; } } } })(); Another book title based on my participation in the O'Reilly Reader Reviews programm. This time we are diving into the depths of online marketing. Whether it's for your own business or providing professional services to your clients as a consultant you definitely should have a go at this book. Following my review as published on Amazon: Practical and efficient guide The book structure is divided in some general background information regarding past, present and maybe future social media networks to keep an eye, then explicit guidelines and marketing strategies on different fields of business, and last but not least great advice on how to get the message out to a broader audience. This allows for quick navigation and using the book as a reference rather than a back-to-back read. Personally, while reading about a particular marketing strategy I went back and forth between other chapters to fill in some interesting side-note and to cook up some ideas. Although, using and running paid advertising services for some of my clients I still came across a big pile of improvements. The authors describe the benefits of shifting away from plain numbers in terms of search engine ranking towards conversions (and therefore earning money) very well. I'm going to keep this title in an easy to reach location on the bookshelf for obvious reasons: It clearly helps me and my clients to create better and more successful marketing campaigns on social media networks. It's a treasure trunk of links to outstanding online resources in order to create better adverts. Lots of practical information about things to avoid \uD83D\uDE09 Surely, a must-read book title for anyone selling products or services online. Create value, trust in your brand and then focus on revenue. if (typeof window !== 'undefined' && window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1') { (window.adsbygoogle = window.adsbygoogle || []).push({}); } (function() { var loaded = false; function loadDisqusEmbed() { if (loaded) return; loaded = true; window.disqus_config = function () { this.page.url = 'social-ecommerce.html'; this.page.identifier = '9271ed1c97_idsocial-ecommerce'; }; var d = document, s = d.createElement('script'); s.src = 'https://getbloggedbyjoki.disqus.com/embed.js'; s.setAttribute('data-timestamp', +new Date()); (d.head || d.body).appendChild(s); } var target = document.getElementById('disqus_thread'); if ('IntersectionObserver' in window && target) { var observer = new IntersectionObserver(function(entries) { if (entries[0].isIntersecting) { loadDisqusEmbed(); observer.disconnect(); } }, { rootMargin: '300px' }); observer.observe(target); } else { window.addEventListener('load', function() { if ('requestIdleCallback' in window) { requestIdleCallback(loadDisqusEmbed, { timeout: 4000 }); } else { setTimeout(loadDisqusEmbed, 3000); } }); } })();"
  },
  "software-development-stack.html": {
    "href": "software-development-stack.html",
    "title": "Software development stack 2012 - Get Blogged by JoKi",
    "summary": "Software development stack 2012 Copy Markdown View Markdown Git