github.com/soulteary/pocket-bookcase@v0.0.0-20240428065142-0b5a9a0fc98a/internal/view/assets/js/page/setting.js (about)

     1  var template = `
     2  <div id="page-setting">
     3      <h1 class="page-header">Settings</h1>
     4      <div class="setting-container">
     5          <details open class="setting-group" id="setting-display">
     6              <summary>Display</summary>
     7              <label>
     8                  <input type="checkbox" v-model="appOptions.ShowId" @change="saveSetting">
     9                  Show bookmark's ID
    10              </label>
    11              <label>
    12                  <input type="checkbox" v-model="appOptions.ListMode" @change="saveSetting">
    13                  Display bookmarks as list
    14              </label>
    15              <label>
    16                  <input type="checkbox" v-model="appOptions.HideThumbnail" @change="saveSetting">
    17                  Hide thumbnail image
    18              </label>
    19              <label>
    20                  <input type="checkbox" v-model="appOptions.HideExcerpt" @change="saveSetting">
    21                  Hide bookmark's excerpt
    22              </label>
    23              <label>
    24                  <input type="checkbox" v-model="appOptions.NightMode" @change="saveSetting">
    25                  Use dark theme
    26              </label>
    27          </details>
    28          <details v-if="activeAccount.owner" open class="setting-group" id="setting-bookmarks">
    29              <summary>Bookmarks</summary>
    30              <label>
    31                  <input type="checkbox" v-model="appOptions.KeepMetadata" @change="saveSetting">
    32                  Keep bookmark's metadata when updating
    33              </label>
    34              <label>
    35                  <input type="checkbox" v-model="appOptions.UseArchive" @change="saveSetting">
    36                  Create archive by default
    37              </label>
    38              <label>
    39                  <input type="checkbox" v-model="appOptions.CreateEbook" @change="saveSetting">
    40                  Create ebook by default
    41              </label>
    42              <label>
    43                  <input type="checkbox" v-model="appOptions.MakePublic" @change="saveSetting">
    44                  Make archive publicly available by default
    45              </label>
    46          </details>
    47          <details v-if="activeAccount.owner" open class="setting-group" id="setting-accounts">
    48              <summary>Accounts</summary>
    49              <ul>
    50                  <li v-if="accounts.length === 0">No accounts registered</li>
    51                  <li v-for="(account, idx) in accounts">
    52                      <p>{{account.username}}
    53                          <span v-if="account.owner" class="account-level">(owner)</span>
    54                      </p>
    55                      <a title="Change password" @click="showDialogChangePassword(account)">
    56                          <i class="fa fas fa-fw fa-key"></i>
    57                      </a>
    58                      <a title="Delete account" @click="showDialogDeleteAccount(account, idx)">
    59                          <i class="fa fas fa-fw fa-trash-alt"></i>
    60                      </a>
    61                  </li>
    62              </ul>
    63              <div class="setting-group-footer">
    64                  <a @click="loadAccounts">Refresh accounts</a>
    65                  <a v-if="activeAccount.owner" @click="showDialogNewAccount">Add new account</a>
    66              </div>
    67          </details>
    68      </div>
    69      <div class="loading-overlay" v-if="loading"><i class="fas fa-fw fa-spin fa-spinner"></i></div>
    70      <custom-dialog v-bind="dialog"/>
    71  </div>`;
    72  
    73  import customDialog from "../component/dialog.js";
    74  import basePage from "./base.js";
    75  
    76  export default {
    77  	template: template,
    78  	mixins: [basePage],
    79  	components: {
    80  		customDialog,
    81  	},
    82  	data() {
    83  		return {
    84  			loading: false,
    85  			accounts: [],
    86  		};
    87  	},
    88  	methods: {
    89  		saveSetting() {
    90  			let options = {
    91  				ShowId: this.appOptions.ShowId,
    92  				ListMode: this.appOptions.ListMode,
    93  				HideThumbnail: this.appOptions.HideThumbnail,
    94  				HideExcerpt: this.appOptions.HideExcerpt,
    95  				NightMode: this.appOptions.NightMode,
    96  			};
    97  
    98  			if (this.activeAccount.owner) {
    99  				options = {
   100  					...options,
   101  					KeepMetadata: this.appOptions.KeepMetadata,
   102  					UseArchive: this.appOptions.UseArchive,
   103  					CreateEbook: this.appOptions.CreateEbook,
   104  					MakePublic: this.appOptions.MakePublic,
   105  				};
   106  			}
   107  
   108  			this.$emit("setting-changed", options);
   109  			//request
   110  			fetch(new URL("api/v1/auth/account", document.baseURI), {
   111  				method: "PATCH",
   112  				body: JSON.stringify({
   113  					config: this.appOptions,
   114  				}),
   115  				headers: {
   116  					"Content-Type": "application/json",
   117  					Authorization: "Bearer " + localStorage.getItem("shiori-token"),
   118  				},
   119  			})
   120  				.then((response) => {
   121  					if (!response.ok) throw response;
   122  					return response.json();
   123  				})
   124  				.then((responseData) => {
   125  					const responseString = JSON.stringify(responseData.message);
   126  					localStorage.setItem("shiori-account", responseString);
   127  				})
   128  				.catch((err) => {
   129  					this.getErrorMessage(err).then((msg) => {
   130  						this.showErrorDialog(msg);
   131  					});
   132  				});
   133  		},
   134  		loadAccounts() {
   135  			if (this.loading) return;
   136  
   137  			this.loading = true;
   138  			fetch(new URL("api/accounts", document.baseURI), {
   139  				headers: {
   140  					"Content-Type": "application/json",
   141  					Authorization: "Bearer " + localStorage.getItem("shiori-token"),
   142  				},
   143  			})
   144  				.then((response) => {
   145  					if (!response.ok) throw response;
   146  					return response.json();
   147  				})
   148  				.then((json) => {
   149  					this.loading = false;
   150  					this.accounts = json;
   151  				})
   152  				.catch((err) => {
   153  					this.loading = false;
   154  					this.getErrorMessage(err).then((msg) => {
   155  						this.showErrorDialog(msg);
   156  					});
   157  				});
   158  		},
   159  		showDialogNewAccount() {
   160  			this.showDialog({
   161  				title: "New Account",
   162  				content: "Input new account's data :",
   163  				fields: [
   164  					{
   165  						name: "username",
   166  						label: "Username",
   167  						value: "",
   168  					},
   169  					{
   170  						name: "password",
   171  						label: "Password",
   172  						type: "password",
   173  						value: "",
   174  					},
   175  					{
   176  						name: "repeat",
   177  						label: "Repeat password",
   178  						type: "password",
   179  						value: "",
   180  					},
   181  					{
   182  						name: "visitor",
   183  						label: "This account is for visitor",
   184  						type: "check",
   185  						value: false,
   186  					},
   187  				],
   188  				mainText: "OK",
   189  				secondText: "Cancel",
   190  				mainClick: (data) => {
   191  					if (data.username === "") {
   192  						this.showErrorDialog("Username must not empty");
   193  						return;
   194  					}
   195  
   196  					if (data.password === "") {
   197  						this.showErrorDialog("Password must not empty");
   198  						return;
   199  					}
   200  
   201  					if (data.password !== data.repeat) {
   202  						this.showErrorDialog("Password does not match");
   203  						return;
   204  					}
   205  
   206  					var request = {
   207  						username: data.username,
   208  						password: data.password,
   209  						owner: !data.visitor,
   210  					};
   211  
   212  					this.dialog.loading = true;
   213  					fetch(new URL("api/accounts", document.baseURI), {
   214  						method: "post",
   215  						body: JSON.stringify(request),
   216  						headers: {
   217  							"Content-Type": "application/json",
   218  							Authorization: "Bearer " + localStorage.getItem("shiori-token"),
   219  						},
   220  					})
   221  						.then((response) => {
   222  							if (!response.ok) throw response;
   223  							return response;
   224  						})
   225  						.then(() => {
   226  							this.dialog.loading = false;
   227  							this.dialog.visible = false;
   228  
   229  							this.accounts.push({
   230  								username: data.username,
   231  								owner: !data.visitor,
   232  							});
   233  							this.accounts.sort((a, b) => {
   234  								var nameA = a.username.toLowerCase(),
   235  									nameB = b.username.toLowerCase();
   236  
   237  								if (nameA < nameB) {
   238  									return -1;
   239  								}
   240  
   241  								if (nameA > nameB) {
   242  									return 1;
   243  								}
   244  
   245  								return 0;
   246  							});
   247  						})
   248  						.catch((err) => {
   249  							this.dialog.loading = false;
   250  							this.getErrorMessage(err).then((msg) => {
   251  								this.showErrorDialog(msg);
   252  							});
   253  						});
   254  				},
   255  			});
   256  		},
   257  		showDialogChangePassword(account) {
   258  			this.showDialog({
   259  				title: "Change Password",
   260  				content: "Input new password :",
   261  				fields: [
   262  					{
   263  						name: "oldPassword",
   264  						label: "Old password",
   265  						type: "password",
   266  						value: "",
   267  					},
   268  					{
   269  						name: "password",
   270  						label: "New password",
   271  						type: "password",
   272  						value: "",
   273  					},
   274  					{
   275  						name: "repeat",
   276  						label: "Repeat password",
   277  						type: "password",
   278  						value: "",
   279  					},
   280  				],
   281  				mainText: "OK",
   282  				secondText: "Cancel",
   283  				mainClick: (data) => {
   284  					if (data.oldPassword === "") {
   285  						this.showErrorDialog("Old password must not empty");
   286  						return;
   287  					}
   288  
   289  					if (data.password === "") {
   290  						this.showErrorDialog("New password must not empty");
   291  						return;
   292  					}
   293  
   294  					if (data.password !== data.repeat) {
   295  						this.showErrorDialog("Password does not match");
   296  						return;
   297  					}
   298  
   299  					var request = {
   300  						username: account.username,
   301  						oldPassword: data.oldPassword,
   302  						newPassword: data.password,
   303  						owner: account.owner,
   304  					};
   305  
   306  					this.dialog.loading = true;
   307  					fetch(new URL("api/accounts", document.baseURI), {
   308  						method: "put",
   309  						body: JSON.stringify(request),
   310  						headers: {
   311  							"Content-Type": "application/json",
   312  							Authorization: "Bearer " + localStorage.getItem("shiori-token"),
   313  						},
   314  					})
   315  						.then((response) => {
   316  							if (!response.ok) throw response;
   317  							return response;
   318  						})
   319  						.then(() => {
   320  							this.dialog.loading = false;
   321  							this.dialog.visible = false;
   322  						})
   323  						.catch((err) => {
   324  							this.dialog.loading = false;
   325  							this.getErrorMessage(err).then((msg) => {
   326  								this.showErrorDialog(msg);
   327  							});
   328  						});
   329  				},
   330  			});
   331  		},
   332  		showDialogDeleteAccount(account, idx) {
   333  			this.showDialog({
   334  				title: "Delete Account",
   335  				content: `Delete account "${account.username}" ?`,
   336  				mainText: "Yes",
   337  				secondText: "No",
   338  				mainClick: () => {
   339  					this.dialog.loading = true;
   340  					fetch(`api/accounts`, {
   341  						method: "delete",
   342  						body: JSON.stringify([account.username]),
   343  						headers: {
   344  							"Content-Type": "application/json",
   345  							Authorization: "Bearer " + localStorage.getItem("shiori-token"),
   346  						},
   347  					})
   348  						.then((response) => {
   349  							if (!response.ok) throw response;
   350  							return response;
   351  						})
   352  						.then(() => {
   353  							this.dialog.loading = false;
   354  							this.dialog.visible = false;
   355  							this.accounts.splice(idx, 1);
   356  						})
   357  						.catch((err) => {
   358  							this.dialog.loading = false;
   359  							this.getErrorMessage(err).then((msg) => {
   360  								this.showErrorDialog(msg);
   361  							});
   362  						});
   363  				},
   364  			});
   365  		},
   366  	},
   367  	mounted() {
   368  		this.loadAccounts();
   369  	},
   370  };