Compare commits

...

9 Commits

6 changed files with 185 additions and 91 deletions

View File

@ -2,8 +2,9 @@
<html> <html>
<head> <head>
<base target="_top" /> <base target="_top" />
<link rel="stylesheet" href="tildefriends.css" />
</head> </head>
<body style="color: #fff"> <body>
<tf-collections-app></tf-collections-app> <tf-collections-app></tf-collections-app>
<script> <script>
window.litDisableBundleWarning = true; window.litDisableBundleWarning = true;

View File

@ -5,6 +5,7 @@ class TfCollectionElement extends LitElement {
static get properties() { static get properties() {
return { return {
whoami: {type: String}, whoami: {type: String},
category: {type: String},
collection: {type: Object}, collection: {type: Object},
selected_id: {type: String}, selected_id: {type: String},
is_creating: {type: Boolean}, is_creating: {type: Boolean},
@ -75,9 +76,10 @@ class TfCollectionElement extends LitElement {
render() { render() {
let self = this; let self = this;
return html` return html`
<span style="display: inline-flex; flex-direction: row"> <link rel="stylesheet" href="tildefriends.css"/>
<span class="inline-flex-row">
<select @change=${this.on_selected} id="select" value=${this.selected_id}> <select @change=${this.on_selected} id="select" value=${this.selected_id}>
<option value="" ?selected=${this.selected_id === ''} disabled hidden>(select)</option> <option value="" ?selected=${this.selected_id === ''} disabled hidden>(select ${this.category})</option>
${Object.values(this.collection ?? {}) ${Object.values(this.collection ?? {})
.sort((x, y) => x.name.localeCompare(y.name)) .sort((x, y) => x.name.localeCompare(y.name))
.map( .map(
@ -91,22 +93,22 @@ class TfCollectionElement extends LitElement {
)} )}
</select> </select>
<span ?hidden=${!this.is_renaming || !this.whoami}> <span ?hidden=${!this.is_renaming || !this.whoami}>
<span style="display: inline-flex; flex-direction: row; margin-left: 8px; margin-right: 8px"> <span class="inline-flex-row" style="margin-left: 8px; margin-right: 8px">
<label for="rename_name">🏷Rename to:</label> <label for="rename_name">🏷Rename to:</label>
<input type="text" id="rename_name"></input> <input type="text" id="rename_name"></input>
<button @click=${this.on_rename}>Rename ${this.type}</button> <button @click=${this.on_rename}>Rename ${this.type}</button>
<button @click=${() => (self.is_renaming = false)}>x</button> <button @click=${() => (self.is_renaming = false)}>x</button>
</span> </span>
</span> </span>
<button @click=${() => (self.is_renaming = true)} ?disabled=${this.is_renaming || !this.selected_id} ?hidden=${!this.whoami}>🏷</button> <button class="yellow" @click=${() => (self.is_renaming = true)} ?disabled=${this.is_renaming || !this.selected_id} ?hidden=${!this.whoami}>🏷</button>
<button @click=${self.on_tombstone} ?disabled=${!this.selected_id} ?hidden=${!this.whoami}>🪦</button> <button class="red" @click=${self.on_tombstone} ?disabled=${!this.selected_id} ?hidden=${!this.whoami}>🪦</button>
<span ?hidden=${!this.is_creating || !this.whoami}> <span ?hidden=${!this.is_creating || !this.whoami}>
<label for="create_name">New ${this.type} name:</label> <label for="create_name">New ${this.type} name:</label>
<input type="text" id="create_name"></input> <input type="text" id="create_name"></input>
<button @click=${this.on_create}>Create ${this.type}</button> <button @click=${this.on_create}>Create ${this.type}</button>
<button @click=${() => (self.is_creating = false)}>x</button> <button @click=${() => (self.is_creating = false)}>x</button>
</span> </span>
<button @click=${() => (self.is_creating = true)} ?hidden=${this.is_creating || !this.whoami}>+</button> <button class="green" @click=${() => (self.is_creating = true)} ?hidden=${this.is_creating || !this.whoami}>+</button>
</span> </span>
`; `;
} }

View File

@ -28,6 +28,7 @@ class TfIdentityPickerElement extends LitElement {
render() { render() {
return html` return html`
<link rel="stylesheet" href="tildefriends.css" />
<select @change=${this.changed} style="max-width: 100%"> <select @change=${this.changed} style="max-width: 100%">
${(this.ids ?? []).map( ${(this.ids ?? []).map(
(id) => (id) =>

View File

@ -255,12 +255,22 @@ class TfCollectionsAppElement extends LitElement {
render() { render() {
let self = this; let self = this;
return html` return html`
<link rel="stylesheet" href="tildefriends.css"/>
<style> <style>
.toc:hover { .toc-item {
white-space: nowrap;
cursor: pointer;
}
.toc-item:hover {
background-color: #0cc; background-color: #0cc;
} }
.toc.selected { .toc-item.selected {
background-color: #088; background-color: #088;
font-weight: bold;
}
.table-of-contents {
flex: 0 0;
margin-right: 16px;
} }
</style> </style>
<div> <div>
@ -272,6 +282,7 @@ class TfCollectionsAppElement extends LitElement {
html`<tf-collection html`<tf-collection
.collection=${this.wikis} .collection=${this.wikis}
whoami=${this.whoami} whoami=${this.whoami}
category="wiki"
selected_id=${this.wiki?.id} selected_id=${this.wiki?.id}
@create=${this.on_wiki_create} @create=${this.on_wiki_create}
@rename=${this.on_wiki_rename} @rename=${this.on_wiki_rename}
@ -284,6 +295,7 @@ class TfCollectionsAppElement extends LitElement {
html`<tf-collection html`<tf-collection
.collection=${this.wiki_docs} .collection=${this.wiki_docs}
whoami=${this.whoami} whoami=${this.whoami}
category="document"
selected_id=${this.wiki_doc && selected_id=${this.wiki_doc &&
this.wiki_doc?.parent == this.wiki?.id this.wiki_doc?.parent == this.wiki?.id
? this.wiki_doc?.id ? this.wiki_doc?.id
@ -298,9 +310,9 @@ class TfCollectionsAppElement extends LitElement {
<div ?hidden=${!this.wiki?.editors || !this.expand_editors}> <div ?hidden=${!this.wiki?.editors || !this.expand_editors}>
<div> <div>
<ul> <ul>
${this.wiki?.editors.map((id) => html`<li><button ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)} ${this.wiki?.editors.map((id) => html`<li><button class="red" ?hidden=${id == this.whoami} @click=${() => self.on_remove_editor(id)}>x</button> ${id}</li>`)}
<li> <li>
<button @click=${() => (self.adding_editor = true)} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button> <button class="green" @click=${() => (self.adding_editor = true)} ?hidden=${this.wiki?.editors?.indexOf(this.whoami) == -1 || this.adding_editor}>+</button>
<div ?hidden=${!this.adding_editor}> <div ?hidden=${!this.adding_editor}>
<label for="add_editor">Add Editor:</label> <label for="add_editor">Add Editor:</label>
<input type="text" id="add_editor"></input> <input type="text" id="add_editor"></input>
@ -312,18 +324,19 @@ class TfCollectionsAppElement extends LitElement {
</div> </div>
</div> </div>
</div> </div>
<div style="display: flex; flex-direction: row"> <div class="flex-row">
<div style="flex: 0 0"> <div class="box table-of-contents">
${Object.values(this.wikis || {}) ${Object.values(this.wikis || {})
.sort((x, y) => x.name.localeCompare(y.name)) .sort((x, y) => x.name.localeCompare(y.name))
.map( .map(
(wiki) => html` (wiki) => html`
<div <div
class="toc ${self.wiki?.id === wiki.id ? 'selected' : ''}" class="toc-item ${self.wiki?.id === wiki.id
style="white-space: nowrap; cursor: pointer" ? 'selected'
: ''}"
@click=${() => self.on_wiki_changed({detail: {value: wiki}})} @click=${() => self.on_wiki_changed({detail: {value: wiki}})}
> >
${wiki.name} ${self.wiki?.id === wiki.id ? '' : '>'} ${wiki.name}
</div> </div>
<ul> <ul>
${Object.values(self.wiki_docs || {}) ${Object.values(self.wiki_docs || {})
@ -332,10 +345,10 @@ class TfCollectionsAppElement extends LitElement {
.map( .map(
(doc) => html` (doc) => html`
<li <li
class="toc ${self.wiki_doc?.id === doc.id class="toc-item ${self.wiki_doc?.id === doc.id
? 'selected' ? 'selected'
: ''}" : ''}"
style="white-space: nowrap; cursor: pointer; list-style: none; text-indent: -1rem" style="list-style: none; text-indent: -1rem"
@click=${() => @click=${() =>
self.on_wiki_doc_changed({detail: {value: doc}})} self.on_wiki_doc_changed({detail: {value: doc}})}
> >

View File

@ -84,7 +84,11 @@ class TfWikiDocElement extends LitElement {
async load_blob() { async load_blob() {
let blob = await tfrpc.rpc.get_blob(this.value?.blob); let blob = await tfrpc.rpc.get_blob(this.value?.blob);
if (blob.endsWith('.box')) { if (!blob) {
console.warn(
"no blob found, we're going to assume the document is empty (load_blob())"
);
} else if (blob.endsWith('.box')) {
let d = await tfrpc.rpc.try_decrypt(this.whoami, blob); let d = await tfrpc.rpc.try_decrypt(this.whoami, blob);
if (d) { if (d) {
blob = d; blob = d;
@ -253,85 +257,43 @@ class TfWikiDocElement extends LitElement {
let self = this; let self = this;
let thumbnail_ref = this.thumbnail(this.blob); let thumbnail_ref = this.thumbnail(this.blob);
return html` return html`
<link rel="stylesheet" href="tildefriends.css"/>
<style> <style>
a:link { a:link { color: #268bd2 }
color: #268bd2; a:visited { color: #6c71c4 }
} a:hover { color: #859900 }
a:visited { a:active { color: #2aa198 }
color: #6c71c4;
} #editor-text-area {
a:hover { background-color: #00000040;
color: #859900; color: white;
} style="flex: 1 1;
a:active { min-height: 10em;
color: #2aa198; font-size: larger;
} ${this.value?.private ? 'border: 4px solid #800' : ''}
</style> </style>
<div style="display: inline-flex; flex-direction: row"> <div class="inline-flex-row">
<button <button ?disabled=${!this.whoami || this.is_editing} @click=${() => (self.is_editing = true)}>Edit</button>
?disabled=${!this.whoami || this.is_editing} <button ?disabled=${this.blob == this.blob_original} @click=${this.on_save_draft}>Save Draft</button>
@click=${() => (self.is_editing = true)} <button ?disabled=${this.blob == this.blob_original && !this.value?.draft} @click=${this.on_publish}>Publish</button>
> <button ?disabled=${!this.is_editing} @click=${this.on_discard}>Discard</button>
Edit <button ?disabled=${!this.is_editing} @click=${() => (self.value = Object.assign({}, self.value, {private: !self.value.private}))}>${this.value?.private ? 'Make Public' : 'Make Private'}</button>
</button> <button ?disabled=${!this.is_editing} @click=${this.on_blog_publish}>Publish Blog</button>
<button
?disabled=${this.blob == this.blob_original}
@click=${this.on_save_draft}
>
Save Draft
</button>
<button
?disabled=${this.blob == this.blob_original && !this.value?.draft}
@click=${this.on_publish}
>
Publish
</button>
<button ?disabled=${!this.is_editing} @click=${this.on_discard}>
Discard
</button>
<button
?disabled=${!this.is_editing}
@click=${() =>
(self.value = Object.assign({}, self.value, {
private: !self.value.private,
}))}
>
${this.value?.private ? 'Make Public' : 'Make Private'}
</button>
<button ?disabled=${!this.is_editing} @click=${this.on_blog_publish}>
Publish Blog
</button>
</div> </div>
<div ?hidden=${!this.value?.private} style="color: #800"> <div ?hidden=${!this.value?.private} style="color: #800">🔒 document is private</div>
🔒 document is private <div class="flex-column" ${this.value?.private ? 'border-top: 4px solid #800' : ''}">
</div>
<div
style="display: flex; flex-direction: row; ${this.value?.private
? 'border-top: 4px solid #800'
: ''}"
>
<textarea <textarea
rows="25"
?hidden=${!this.is_editing} ?hidden=${!this.is_editing}
style="flex: 1 1; min-height: 10em; ${this.value?.private id="editor-text-area"
? 'border: 4px solid #800'
: ''}"
@input=${this.on_edit} @input=${this.on_edit}
@paste=${this.paste} @paste=${this.paste}
.value=${this.blob ?? ''} .value=${this.blob ?? ''}></textarea>
></textarea> <div style="flex: 1 1; margin-top: 16px">
<div style="flex: 1 1"> <div ?hidden=${!this.is_editing} class="box">
<div Summary
?hidden=${!this.is_editing} <img ?hidden=${!thumbnail_ref} style="max-width: 128px; max-height: 128px; float: right" src="/${thumbnail_ref}/view">
style="border: 1px solid #fff; border-radius: 1em; padding: 0.5em" <h1 ?hidden=${!this.title(this.blob)}>${unsafeHTML(this.markdown(this.title(this.blob)))}</h1>
>
<img
?hidden=${!thumbnail_ref}
style="max-width: 128px; max-height: 128px; float: right"
src="/${thumbnail_ref}/view"
/>
<h1 ?hidden=${!this.title(this.blob)}>
${unsafeHTML(this.markdown(this.title(this.blob)))}
</h1>
${unsafeHTML(this.markdown(this.summary(this.blob)))} ${unsafeHTML(this.markdown(this.summary(this.blob)))}
</div> </div>
${unsafeHTML(this.markdown(this.blob))} ${unsafeHTML(this.markdown(this.blob))}

115
apps/wiki/tildefriends.css Normal file
View File

@ -0,0 +1,115 @@
/*
* Tilde Friends core stylesheet
* This is a prototype; things may change based on feedback.
*
* This Software is an external library that is part of
* Tilde Friends and is shared under the MIT license.
*
* Inject this file in your app at tildefriends.css
* and use this tag to import it:
* <link rel="stylesheet" href="tildefriends.css"/>
*
* Revision 0 / 2024 M02 19
*/
body {
color: white;
font-family: sans-serif;
}
button,
.button,
input[type='button'],
input[type='submit'],
select {
border: none;
border-radius: 8px;
padding: 8px 12px;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 4px;
&.red {
background-color: #bd1e24;
color: white;
}
&.green {
background-color: #18922d;
color: white;
}
&.blue {
background-color: #0067a7;
color: white;
}
&.yellow {
background-color: #ee9600;
color: black;
}
&:hover {
filter: brightness(0.75);
}
}
a:link {
color: #268bd2;
}
a:visited {
color: #6c71c4;
}
a:hover {
color: #859900;
}
a:active {
color: #2aa198;
}
table {
border-collapse: collapse;
width: 100%;
}
td,
th {
border: 1px solid #ffffff40;
text-align: left;
padding: 8px;
}
tr:nth-child(even) {
background-color: #ffffff20;
}
.flex {
display: flex;
}
.flex-column {
display: flex;
flex-direction: column;
}
.flex-row {
display: flex;
flex-direction: row;
}
.inline-flex-row {
display: inline-flex;
flex-direction: row;
}
.box {
background-color: #00000020;
border: 1px solid grey;
border-radius: 8px;
padding: 16px;
margin: 4px;
}