Merge branch 'tasiaiso-wiki-improvements'
This commit is contained in:
		| @@ -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; | ||||||
|   | |||||||
| @@ -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> | ||||||
| 		`; | 		`; | ||||||
| 	} | 	} | ||||||
|   | |||||||
| @@ -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) => | ||||||
|   | |||||||
| @@ -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}})} | ||||||
| 												> | 												> | ||||||
|   | |||||||
| @@ -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
									
								
							
							
						
						
									
										115
									
								
								apps/wiki/tildefriends.css
									
									
									
									
									
										Normal 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; | ||||||
|  | } | ||||||
		Reference in New Issue
	
	Block a user