reintroduce bitty and add progressive enhancement and tabs

This commit is contained in:
yagich committed 2026-05-28 06:41:59 +03:00
1 parent d2ea0bbd51
commit 160629fca7
6 files changed
+65 -16

No files matched your search

+3
View File
@@ -13,6 +13,8 @@
{%- endif -%}
</head>
<body>
<bitty-8 data-connect="/static/js/bits/progressive-enhancement.js"></bitty-8>
<bitty-8 data-connect="/static/js/bits/ui.js"></bitty-8>
{%- include 'common/topnav.html' -%}
{%- with messages = get_flashed_messages(with_categories=true) -%}
{%- if messages -%}
@@ -23,6 +25,7 @@
{%- endwith -%}
{%- block content -%}{%- endblock -%}
{%- include 'common/footer.html' -%}
<script type="module" src="/static/js/vnd/bitty-8.0.0.js"></script>
<script src="{{'/static/js/ui.js' | cachebust}}"></script>
</body>
</html>
+16 -16
View File
@@ -73,10 +73,10 @@
{%- endmacro %}
{% macro tabs(prefix='', labels = []) -%}
<div class="tab-container">
<div class="tab-container" data-r="setTab">
<div class="tab-bar" role="tablist">
{%- for tab_label in labels -%}
<button type="button" class="tab-button" role="tab" aria-selected="{{'true' if loop.index0==0 else 'false'}}" id="{{prefix+'-'+(tab_label | lower)+'-tab'}}" aria-controls="{{prefix+'-'+(tab_label | lower)+'-content'}}" disabled>{{tab_label}}</button>
<button type="button" class="tab-button" role="tab" aria-selected="{{'true' if loop.index0==0 else 'false'}}" id="{{prefix+'-'+(tab_label | lower)+'-tab'}}" aria-controls="{{prefix+'-'+(tab_label | lower)+'-content'}}" disabled data-r="enhance" data-s="setTab" data-tab-index="{{loop.index0}}">{{tab_label}}</button>
{%- endfor -%}
</div>
{%- for tab_label in labels -%}
@@ -97,16 +97,16 @@
{%- call(idx) tabs(prefix='babycode', labels=['Write', 'Preview']) -%}
{%- if idx == 0 -%}
<span class="babycode-editor-controls">
<span class="button-row">
<button type="button" class="minimal" disabled><b>B</b></button>
<button type="button" class="minimal" disabled><i>i</i></button>
<button type="button" class="minimal" disabled><s>S</s></button>
<button type="button" class="minimal" disabled><u>U</u></button>
<button type="button" class="minimal" disabled><code>://</code></button>
<button type="button" class="minimal" disabled><code>&lt;/&gt;</code></button>
<button type="button" class="minimal" disabled>1.</button>
<button type="button" class="minimal" disabled>&bullet;</button>
<button type="button" class="minimal" disabled><img src="/static/emoji/angry.png" class="emoji"></button>
<span class="button-row js-only" data-r="enhance">
<button type="button" class="minimal" data-babycode-tag="b" data-s="insertBabycode"><b>B</b></button>
<button type="button" class="minimal" data-babycode-tag="i" data-s="insertBabycode"><i>i</i></button>
<button type="button" class="minimal" data-babycode-tag="s" data-s="insertBabycode"><s>S</s></button>
<button type="button" class="minimal" data-babycode-tag="u" data-s="insertBabycode"><u>U</u></button>
<button type="button" class="minimal" data-babycode-tag="url" data-s="insertBabycode"><code>://</code></button>
<button type="button" class="minimal" data-babycode-tag="code" data-s="insertBabycode"><code>&lt;/&gt;</code></button>
<button type="button" class="minimal" data-babycode-tag="ol" data-s="insertBabycode">1.</button>
<button type="button" class="minimal" data-babycode-tag="ul" data-s="insertBabycode">&bullet;</button>
<button type="button" class="minimal"><img src="/static/emoji/angry.png" class="emoji"></button>
</span>
<span class="flex-last">{# stub: char count #}</span>
</span>
@@ -183,12 +183,12 @@
<a class="linkbutton" href="{{url_for('posts.edit', post_id=post.id, _anchor='babycode-content')}}">Edit</a>
{%- endif -%}
{%- if can_reply -%}
<button disabled title="This feature requires JavaScript to be enabled.">Quote</button>
<button data-r="enhance" disabled title="This feature requires JavaScript to be enabled.">Quote</button>
{%- endif -%}
{%- if can_delete -%}
<a class="linkbutton critical" href="{{url_for('posts.delete', post_id=post.id)}}">Delete</a>
{%- endif -%}
<button disabled title="This feature requires JavaScript to be enabled.">{{icn_bookmark(24)}}Bookmark&hellip;</button>
<button data-r="enhance" disabled title="This feature requires JavaScript to be enabled.">{{icn_bookmark(24)}}Bookmark&hellip;</button>
</span>
{%- endif -%}
</div>
@@ -213,10 +213,10 @@
{% set reactors_str = reactors_str + '\n...and many others' %}
{% endif %}
{% set has_reacted = get_active_user() is not none and get_active_user().username in reactors %}
<button type="button" disabled title="{{reactors_str}}" class="minimal {{'alt' if has_reacted else ''}}"><img src="/static/emoji/{{reaction.reaction_text}}.png">{{reaction.c}}</button>
<button data-r="enhance" type="button" disabled title="{{reactors_str}}" class="minimal {{'alt' if has_reacted else ''}}"><img src="/static/emoji/{{reaction.reaction_text}}.png">{{reaction.c}}</button>
{%- endfor -%}
</span>
{%- if is_logged_in() and allow_reacting -%}<button disabled title="This feature requires JavaScript to be enabled.">Add reaction</button>{%- endif -%}
{%- if is_logged_in() and allow_reacting -%}<button data-r="enhance" disabled title="This feature requires JavaScript to be enabled.">Add reaction</button>{%- endif -%}
{%- elif is_editing -%}
<input type="submit" value="Save">
<a href="{{get_post_url(post.id, _anchor=true)}}" class="linkbutton warn">Cancel</a>
+3
View File
@@ -905,6 +905,9 @@ ol.sortable-list {
}
}
.js-only {
display: none;
}
@media (max-width: 768px) {
body {
@@ -0,0 +1,17 @@
export const b = {
init: 'enhance',
}
export function enhance(_, __, el) {
console.log(el);
if (el.classList.contains('js-only')) {
el.classList.remove('js-only');
}
if (el.disabled) {
el.disabled = false;
if (el.title.search('JavaScript') !== -1) {
el.title = '';
}
}
}
+25
View File
@@ -0,0 +1,25 @@
export const b = {}
export function setTab(_, sender, el) {
if (sender.ariaSelected === 'true') {
return;
}
if (!el.contains(sender)) {
return;
}
const tabIndex = parseInt(sender.dataset.tabIndex);
const tabPanels = el.querySelectorAll('.tab-content');
const tabButtons = el.querySelectorAll('.tab-bar button');
for (let i = 0; i < tabPanels.length; i++) {
const tabPanel = tabPanels[i];
const tabButton = tabButtons[i];
if (i === tabIndex) {
tabPanel.classList.remove('hidden');
tabButton.ariaSelected = 'true';
} else if (!tabPanel.classList.contains('hidden')) {
tabPanel.classList.add('hidden');
tabButton.ariaSelected = 'false';
}
}
}
+1
View File
@@ -0,0 +1 @@
const t=["checkbox","color","date","datetime-local","file","form","option","radio","search","time"],e=["email","month","number","password","search","tel","text","url","week"],s=["range","search","number"],n="bitty_store";class r extends HTMLElement{static bits=[];constructor(){super()}async connectedCallback(){if(this.dataset.connect){const t=this.dataset.connect.trim(),s=await import(t);void 0!==s.b&&(s.b._trueValues=["true","yes","on","1"],s.b._falseValues=["false","no","off","0"],s.b._debouncers={},s.b._marks={},s.b.svgs={},void 0===s.b.templates&&(s.b.templates={}),void 0===s.b.config&&(s.b.config={}),void 0===s.b.config.getState&&(s.b.config.getState={attributes:["aria-autoComplete","aria-checked","aria-disabled","aria-expended","aria-hidden","aria-pressed","aria-read-only","aria-selected","aria-value-now","aria-value-text"],keys:["checked","diabled","hidden","readOnly","selected","spellcheck","value"]}),this.addToggleSwitchTemplate(s),s.b.data={},this.loadPageAssets(s),this.addBittyClasses(s),this.constructor.bits.push(s),window.addEventListener("bittyforwardsender",t=>{s.b._processBittyForwardSender(t)}),window.addEventListener("bittysend",t=>{s.b._processBittySend(t)}),window.addEventListener("bittytrigger",t=>{s.b._processBittyTrigger(t)}),window.addEventListener("click",t=>{s.b._processEvent(t)}),window.addEventListener("input",t=>{s.b._processInputEvent(t)}),window.addEventListener("change",t=>{s.b._processChangeEvent(t)}),document.addEventListener("submit",t=>{s.b._processEvent(t)}),document.addEventListener("keydown",t=>{if(13===t.keyCode&&t.target&&t.target.tagName&&"input"===t.target.tagName.toLowerCase()){const n=t.target.getAttribute("type");e.includes(n.toLowerCase())&&s.b._processInputTextEnter(t)}}),[...document.querySelectorAll("[data-listen]")].forEach(t=>{s.b._splitSignalString(t.dataset.listen).forEach(t=>{!1===["bittysend","bittytrigger","change","click","input","submit"].includes(t)&&window.addEventListener(t,t=>{s.b._processEvent(t)})})}),s.b._processInit())}}addBittyClasses(t){Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(t=>"_"===t.substring(0,1)).forEach(e=>{t.b[e.substring(1)]=this[e].bind(t)})}_addListener(t,e){window.addEventListener(t,t=>{this.b._processCustomEvent(t,e)})}_addStyles(t){const e=new CSSStyleSheet;return e.replaceSync(t),document.adoptedStyleSheets.push(e),e}addToggleSwitchTemplate(t){t.b.templates.switch='\n<label for="__ID__" class="__CLASS__"__KEY_ATTR____LABLE_MISC__>\n __PREPEND__\n <button id="__ID__" role="switch"__SEND_ATTR____RECEIVE_ATTR____KEY_ATTR____SAVE_ATTR__ aria-checked="__STATE__"__BUTTON_MISC__>\n <span></span><span></span>\n </button>\n __APPEND__\n</label>'}_ce(t,e={}){return document.createElement(t,e)}async _copy(t){const e=document.querySelector(t);if(void 0!==e.value&&""!==e.value)try{await navigator.clipboard.writeText(e.value)}catch(e){return console.error(`Could not copy .value from ${t}`),!1}else try{await navigator.clipboard.writeText(e.innerText)}catch(e){return console.error(`Could not copy .innerHTML from ${t}`),!1}return!0}_debounce(t,e,s,n={}){this.b._debouncers[t]&&window.clearTimeout(this.b._debouncers[t]),this.b._debouncers[t]=setTimeout(()=>{this.b.send.apply(this,[n,e])},s)}_dedupe(t){return[...new Set(t)]}async _deletePageData(t){const e=await this.b._initPageDB();return new Promise((s,r)=>{const i=e.transaction(n,"readwrite").objectStore(n).delete(t);i.onsuccess=()=>s(i.result),i.onerror=()=>r(i.result)})}async _deleteSiteData(t){const e=await this.b._initSiteDB();return new Promise((s,r)=>{const i=e.transaction(n,"readwrite").objectStore(n).delete(t);i.onsuccess=()=>s(i.result),i.onerror=()=>r(i.result)})}async __deleteValueFromSiteDB(t){const e=await this.b._initSiteDB();return new Promise((s,r)=>{const i=e.transaction(n,"readwrite").objectStore(n).delete(t);i.onsuccess=()=>s(i.result),i.onerror=()=>r(i.result)})}__findSenders(t){const e=[];for(;t;)void 0!==t.dataset&&void 0!==t.dataset.s&&e.push(t),t=t.parentElement;return e}_forwardSender(t,e){const s=new i(t,e);dispatchEvent(s)}async _getData(t,e=void 0,s={}){let n=await fetch(t,s);try{if(!0===n.ok)try{return await n.json()}catch(t){console.error(t)}else console.error(n)}catch(t){console.error(t)}}_getMarks(t){return this.b._marks[t]}async _getTemplates(t,e={}){let s=await fetch(t,e);try{if(!0===s.ok)try{const t=await s.text(),e=document.createElement("div");return e.innerHTML=t,e.querySelectorAll("script").forEach(t=>{"text/html"===t.type&&void 0!==t.dataset.template&&(this.b.templates[t.dataset.template]=t.innerHTML.trim()),"image/svg"===t.type&&void 0!==t.dataset.template&&(this.b.svgs[t.dataset.template]=t.innerHTML.trim()),"application/json"===t.type&&void 0!==t.dataset.template&&(this.b.data[t.dataset.template]=JSON.parse(t.innerHTML.trim()))}),!0}catch(t){return console.error(t),!1}}catch(t){return console.error(t),!1}}__getBool(t){if(void 0===t)return;if(null===t)return;const e=parseInt(t,10);if(NaN!==e&&e>0)return!0;if(NaN!==e&&e<Line truncated