From c7d3474ba8cb49ab0f1978216d80b08ec2c8e5d7 Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sat, 16 Sep 2023 12:40:19 +0200 Subject: [PATCH 01/19] feat(explorer): add config to support custom sort fn --- quartz/components/Explorer.tsx | 13 ++++++++++++- quartz/components/ExplorerNode.tsx | 21 ++++++++------------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index ce69491..ee0f96f 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -11,6 +11,17 @@ const defaultOptions = (): Options => ({ folderClickBehavior: "collapse", folderDefaultState: "collapsed", useSavedState: true, + // Sort order: folders first, then files. Sort folders and files alphabetically + sortFn: (a, b) => { + if ((!a.file && !b.file) || (a.file && b.file)) { + return a.name.localeCompare(b.name) + } + if (a.file && !b.file) { + return 1 + } else { + return -1 + } + }, }) export default ((userOpts?: Partial) => { function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) { @@ -22,7 +33,7 @@ export default ((userOpts?: Partial) => { allFiles.forEach((file) => fileTree.add(file, 1)) // Sort tree (folders first, then files (alphabetic)) - fileTree.sort() + fileTree.sort(opts.sortFn!) // Get all folders of tree. Initialize with collapsed state const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed") diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index 6718ec9..4d00103 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -7,6 +7,7 @@ export interface Options { folderDefaultState: "collapsed" | "open" folderClickBehavior: "collapse" | "link" useSavedState: boolean + sortFn: (a: FileNode, b: FileNode) => number } type DataWrapper = { @@ -90,19 +91,13 @@ export class FileNode { } // Sort order: folders first, then files. Sort folders and files alphabetically - sort() { - this.children = this.children.sort((a, b) => { - if ((!a.file && !b.file) || (a.file && b.file)) { - return a.name.localeCompare(b.name) - } - if (a.file && !b.file) { - return 1 - } else { - return -1 - } - }) - - this.children.forEach((e) => e.sort()) + /** + * Sorts tree according to sort/compare function + * @param sortFn compare function used for `.sort()`, also used recursively for children + */ + sort(sortFn: (a: FileNode, b: FileNode) => number) { + this.children = this.children.sort(sortFn) + this.children.forEach((e) => e.sort(sortFn)) } } From 58aea1cb0791e18cd092d88de5374431eba7f1d3 Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sat, 16 Sep 2023 17:28:58 +0200 Subject: [PATCH 02/19] feat: implement filter function for explorer --- quartz/components/ExplorerNode.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index 4d00103..40e526a 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -66,6 +66,21 @@ export class FileNode { this.children.forEach((e) => e.print(depth + 1)) } + filter(filterFn: (node: FileNode) => boolean) { + const filteredNodes: FileNode[] = [] + + const traverse = (node: FileNode) => { + if (filterFn(node)) { + filteredNodes.push(node) + } + node.children.forEach(traverse) + } + + traverse(this) + + this.children = filteredNodes + } + /** * Get folder representation with state of tree. * Intended to only be called on root node before changes to the tree are made From 036a33f70bcabc17469956740847796a5f13b9ab Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sat, 16 Sep 2023 17:47:44 +0200 Subject: [PATCH 03/19] fix: use correct import for `QuartzPluginData` --- quartz/components/ExplorerNode.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index 40e526a..d962425 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -1,5 +1,5 @@ // @ts-ignore -import { QuartzPluginData } from "vfile" +import { QuartzPluginData } from "../plugins/vfile" import { resolveRelative } from "../util/path" export interface Options { From 31d16fbd2c82380af586e458b2c1ff29b90b53ae Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sat, 16 Sep 2023 19:18:59 +0200 Subject: [PATCH 04/19] feat(explorer): integrate filter option --- quartz/components/Explorer.tsx | 5 +++++ quartz/components/ExplorerNode.tsx | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index ee0f96f..efc9f6a 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -35,6 +35,11 @@ export default ((userOpts?: Partial) => { // Sort tree (folders first, then files (alphabetic)) fileTree.sort(opts.sortFn!) + // If provided, apply filter function to fileTree + if (opts.filterFn) { + fileTree.filter(opts.filterFn) + } + // Get all folders of tree. Initialize with collapsed state const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed") diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index d962425..5cf3f01 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -8,6 +8,7 @@ export interface Options { folderClickBehavior: "collapse" | "link" useSavedState: boolean sortFn: (a: FileNode, b: FileNode) => number + filterFn?: (node: FileNode) => boolean } type DataWrapper = { @@ -66,6 +67,10 @@ export class FileNode { this.children.forEach((e) => e.print(depth + 1)) } + /** + * Filter FileNode tree. Behaves similar to `Array.prototype.filter()`, but modifies tree in place + * @param filterFn function to filter tree with + */ filter(filterFn: (node: FileNode) => boolean) { const filteredNodes: FileNode[] = [] From 3d8c470c0d298f720614318fb4c14575e72bbd2e Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sat, 16 Sep 2023 19:35:27 +0200 Subject: [PATCH 05/19] feat(explorer): implement `map` fn argument Add a function for mapping over all FileNodes as an option for `Explorer` --- quartz/components/Explorer.tsx | 5 +++++ quartz/components/ExplorerNode.tsx | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index efc9f6a..23c5db2 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -40,6 +40,11 @@ export default ((userOpts?: Partial) => { fileTree.filter(opts.filterFn) } + // If provided, apply map function to fileTree + if (opts.mapFn) { + fileTree.map(opts.mapFn) + } + // Get all folders of tree. Initialize with collapsed state const folders = fileTree.getFolderPaths(opts.folderDefaultState === "collapsed") diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index 5cf3f01..b8d8c14 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -9,6 +9,7 @@ export interface Options { useSavedState: boolean sortFn: (a: FileNode, b: FileNode) => number filterFn?: (node: FileNode) => boolean + mapFn?: (node: FileNode) => void } type DataWrapper = { @@ -86,6 +87,16 @@ export class FileNode { this.children = filteredNodes } + /** + * Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place + * @param mapFn function to filter tree with + */ + map(mapFn: (node: FileNode) => void) { + mapFn(this) + + this.children.forEach((child) => child.map(mapFn)) + } + /** * Get folder representation with state of tree. * Intended to only be called on root node before changes to the tree are made From fea352849c6972da4b3b8935eb2e86f6cefc76ed Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sat, 16 Sep 2023 19:45:21 +0200 Subject: [PATCH 06/19] fix: create deep copy of file passed into tree --- quartz/components/ExplorerNode.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index b8d8c14..e1c8b8e 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -32,7 +32,7 @@ export class FileNode { constructor(name: string, file?: QuartzPluginData, depth?: number) { this.children = [] this.name = name - this.file = file ?? null + this.file = file ? structuredClone(file) : null this.depth = depth ?? 0 } From f7029012dfb73ce04405bfe44e4e4d984818bf5f Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sat, 16 Sep 2023 21:58:38 +0200 Subject: [PATCH 07/19] feat: black magic add config for `order` array, which determines the order in which all passed config functions for explorer will get executed in. functions will now dynamically be called on `fileTree` via array accessor (e.g. fileTree["sort"].call(...)) with corresponding function from options being passed to call) --- quartz/components/Explorer.tsx | 35 ++++++++++++++++++++++-------- quartz/components/ExplorerNode.tsx | 3 +++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 23c5db2..346bd75 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -22,6 +22,7 @@ const defaultOptions = (): Options => ({ return -1 } }, + order: ["filter", "map", "sort"], }) export default ((userOpts?: Partial) => { function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) { @@ -32,17 +33,33 @@ export default ((userOpts?: Partial) => { const fileTree = new FileNode("") allFiles.forEach((file) => fileTree.add(file, 1)) - // Sort tree (folders first, then files (alphabetic)) - fileTree.sort(opts.sortFn!) - - // If provided, apply filter function to fileTree - if (opts.filterFn) { - fileTree.filter(opts.filterFn) + /** + * Keys of this object must match corresponding function name of `FileNode`, + * while values must be the argument that will be passed to the function. + * + * e.g. entry for FileNode.sort: `sort: opts.sortFn` (value is sort function from options) + */ + const functions = { + map: opts.mapFn, + sort: opts.sortFn, + filter: opts.filterFn, } - // If provided, apply map function to fileTree - if (opts.mapFn) { - fileTree.map(opts.mapFn) + // Execute all functions (sort, filter, map) that were provided (if none were provided, only default "sort" is applied) + if (opts.order) { + // Order is important, use loop with index instead of order.map() + for (let i = 0; i < opts.order.length; i++) { + const functionName = opts.order[i] + if (functions[functionName]) { + // for every entry in order, call matching function in FileNode and pass matching argument + // e.g. i = 0; functionName = "filter" + // converted to: (if opts.filterFn) => fileTree.filter(opts.filterFn) + + // @ts-ignore + // typescript cant statically check these dynamic references, so manually make sure reference is valid and ignore warning + fileTree[functionName].call(fileTree, functions[functionName]) + } + } } // Get all folders of tree. Initialize with collapsed state diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index e1c8b8e..b181744 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -2,6 +2,8 @@ import { QuartzPluginData } from "../plugins/vfile" import { resolveRelative } from "../util/path" +type OrderEntries = "sort" | "filter" | "map" + export interface Options { title: string folderDefaultState: "collapsed" | "open" @@ -10,6 +12,7 @@ export interface Options { sortFn: (a: FileNode, b: FileNode) => number filterFn?: (node: FileNode) => boolean mapFn?: (node: FileNode) => void + order?: OrderEntries[] } type DataWrapper = { From 9358f73f1c939ce459d7835457527e35e1bdf857 Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sun, 17 Sep 2023 12:41:06 +0200 Subject: [PATCH 08/19] fix: display name for file nodes --- quartz/components/ExplorerNode.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index b181744..e2d8871 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -160,7 +160,7 @@ export function ExplorerNode({ node, opts, fullPath, fileData }: ExplorerNodePro // Single file node
  • - {node.file.frontmatter?.title} + {node.name}
  • ) : ( From 94a04ab1c9fd099c808f3f4e6633722e0d13ac85 Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sun, 17 Sep 2023 15:51:08 +0200 Subject: [PATCH 09/19] fix(explorer): filter function in `ExplorerNode` --- quartz/components/ExplorerNode.tsx | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index e2d8871..f8b99f0 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -76,18 +76,8 @@ export class FileNode { * @param filterFn function to filter tree with */ filter(filterFn: (node: FileNode) => boolean) { - const filteredNodes: FileNode[] = [] - - const traverse = (node: FileNode) => { - if (filterFn(node)) { - filteredNodes.push(node) - } - node.children.forEach(traverse) - } - - traverse(this) - - this.children = filteredNodes + this.children = this.children.filter(filterFn) + this.children.forEach((child) => child.filter(filterFn)) } /** From 5cc9253c41fda87ba473df7023567ba66ce3c32b Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sun, 17 Sep 2023 16:41:23 +0200 Subject: [PATCH 10/19] docs(explorer): write docs for new features --- docs/features/explorer.md | 198 +++++++++++++++++++++++++++++++++++++- 1 file changed, 195 insertions(+), 3 deletions(-) diff --git a/docs/features/explorer.md b/docs/features/explorer.md index 17647de..8a9f506 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -4,9 +4,9 @@ tags: - component --- -Quartz features an explorer that allows you to navigate all files and folders on your site. It supports nested folders and has options for customization. +Quartz features an explorer that allows you to navigate all files and folders on your site. It supports nested folders and is highly customizable. -By default, it will show all folders and files on your page. To display the explorer in a different spot, you can edit the [[layout]]. +By default, it shows all folders and files on your page. To display the explorer in a different spot, you can edit the [[layout]]. > [!info] > The explorer uses local storage by default to save the state of your explorer. This is done to ensure a smooth experience when navigating to different pages. @@ -25,6 +25,14 @@ Component.Explorer({ folderClickBehavior: "collapse", // what happens when you click a folder ("link" to navigate to folder page on click or "collapse" to collapse folder on click) folderDefaultState: "collapsed", // default state of folders ("collapsed" or "open") useSavedState: true, // wether to use local storage to save "state" (which folders are opened) of explorer + // Sort order: folders first, then files. Sort folders and files alphabetically + sortFn: (a, b) => { + ... // default implementation shown later + }, + filterFn: undefined, + mapFn: undefined, + // what order to apply functions in + order: ["filter", "map", "sort"], }) ``` @@ -33,9 +41,193 @@ When passing in your own options, you can omit any or all of these fields if you Want to customize it even more? - Removing table of contents: remove `Component.Explorer()` from `quartz.layout.ts` - - (optional): After removing the explorer component, you can move the [[table of contents]] component back to the `left` part of the layout + - (optional): After removing the explorer component, you can move the [[table of contents | Table of Contents]] component back to the `left` part of the layout +- Changing `sort`, `filter` and `map` behavior: explained in [[explorer#Advanced customization]] - Component: - Wrapper (Outer component, generates file tree, etc): `quartz/components/Explorer.tsx` - Explorer node (recursive, either a folder or a file): `quartz/components/ExplorerNode.tsx` - Style: `quartz/components/styles/explorer.scss` - Script: `quartz/components/scripts/explorer.inline.ts` + +## Advanced customization + +This component allows you to fully customize all of its behavior. You can pass a custom `sort`, `filter` and `map` function. +All functions you can pass work with the `FileNode` class, which has the following properties: + +```ts title="quartz/components/ExplorerNode.tsx" {2-5} +export class FileNode { + children: FileNode[] // children of current node + name: string // name of node (only useful for folders) + file: QuartzPluginData | null // set if node is a file, see `QuartzPluginData` for more detail + depth: number // depth of current node + + ... // rest of implementation +} +``` + +Every function you can pass is optional. By default, only a `sort` function will be used: + +```ts title="Default sort function" +// Sort order: folders first, then files. Sort folders and files alphabetically +Component.Explorer({ + sortFn: (a, b) => { + if ((!a.file && !b.file) || (a.file && b.file)) { + return a.name.localeCompare(b.name) + } + if (a.file && !b.file) { + return 1 + } else { + return -1 + } + }, +}) +``` + +--- + +You can pass your own functions for `sortFn`, `filterFn` and `mapFn`. All functions will be executed in the order provided by the `order` option (see [[explorer#Customization]]). These functions behave similarly to their `Array.prototype` counterpart, except they modify the entire `FileNode` tree in place instead of returning a new one. + +For more information on how to use `sort`, `filter` and `map`, you can check [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) and [Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). + +Type definitions look like this: + +```ts +sortFn: (a: FileNode, b: FileNode) => number +filterFn: (node: FileNode) => boolean +mapFn: (node: FileNode) => void +``` + +> [!tip] +> You can check if a `FileNode` is a folder or a file like this: +> +> ```ts +> if (node.file) { +> // node is a file +> } else { +> // node is a folder +> } +> ``` + +## Basic examples + +These examples show the basic usage of `sort`, `map` and `filter`. + +### Use `sort` to put files first + +Using this example, the explorer will alphabetically sort everything, but put all **files** above all **folders**. + +```ts title="quartz.layout.ts" +Component.Explorer({ + sortFn: (a, b) => { + if ((!a.file && !b.file) || (a.file && b.file)) { + return a.name.localeCompare(b.name) + } + if (a.file && !b.file) { + return -1 + } else { + return 1 + } + }, +}) +``` + +### Change display names (`map`) + +Using this example, the display names of all `FileNodes` (folders + files) will be converted to full upper case. + +```ts title="quartz.layout.ts" +Component.Explorer({ + mapFn: (node) => { + node.name = node.name.toUpperCase() + }, +}) +``` + +### Remove list of elements (`filter`) + +Using this example, you can remove elements from your explorer by providing a list of folders/files using the `list` array. + +```ts title="quartz.layout.ts" +Component.Explorer({ + filterFn: (node) => { + // list containing names of everything you want to filter out + const list = ["authoring content", "building your", "tags", "hosting"] + + for (let listNodeName of list) { + if (listNodeName.toLowerCase() === node.name.toLowerCase()) { + return false // Found a match, so return false to filter out the node + } + } + return true // No match found, so return true to keep the node + }, +}) +``` + +You can customize this by changing the entries of the `list` array. Simply add all folder or file names you want to remove to the array (case insensitive). + +## Advanced examples + +### Add emoji prefix + +To add emoji prefixes (📁 for folders, 📄 for files), you could use a map function like this: + +```ts title="quartz.layout.ts" +Component.Explorer({ + mapFn: (node) => { + // dont change name of root node + if (node.depth > 0) { + // set emoji for file/folder + if (node.file) { + node.name = "📄 " + node.name + } else { + node.name = "📁 " + node.name + } + } + }, +}}) +``` + +### Putting it all together + +In this example, we're going to customize the explorer by using functions from examples above to [[explorer#Add emoji prefix | add emoji prefixes]], [[explorer#remove-list-of-elements-filter| filter out some folders]] and [[explorer#use-sort-to-put-files-first | sort with files above folders]]. + +```ts title="quartz.layout.ts" +Component.Explorer({ + filterFn: sampleFilterFn, + mapFn: sampleMapFn, + sortFn: sampleSortFn, + order: ["filter", "sort", "map"], +}) +``` + +Notice how we customized the `order` array here. This is done because the default order applies the `sort` function last. While this normally works well, it would cause unintended behavior here, since we changed the first characters of all display names. In our example, `sort` would be applied based off the emoji prefix instead of the first _real_ character. + +To fix this, we just changed around the order and apply the `sort` function before changing the display names in the `map` function. + +> [!tip] +> When writing more complicated functions, the `layout` file can start to look very cramped. +> You can fix this by defining your functions in another file. +> +> ```ts title="functions.ts" +> import { Options } from "./quartz/components/ExplorerNode" +> export const mapFn: Options["mapFn"] = (node) => { +> // implement your function here +> } +> export const filterFn: Options["filterFn"] = (node) => { +> // implement your function here +> } +> export const sortFn: Options["sortFn"] = (a, b) => { +> // implement your function here +> } +> ``` +> +> You can then import them like this: +> +> ```ts title="quartz.layout.ts" +> import { mapFn, filterFn, sortFn } from "./path/to/your/functions" +> Component.Explorer({ +> mapFn: mapFn, +> filterFn: filterFn, +> sortFn: sortFn, +> }) +> ``` From af41f34bfd4126756e594ce4d6a46d4f4907754b Mon Sep 17 00:00:00 2001 From: Christian Gill Date: Sun, 17 Sep 2023 20:02:00 +0200 Subject: [PATCH 11/19] fix(slug): Handle question mark (#481) --- quartz/util/path.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quartz/util/path.ts b/quartz/util/path.ts index 1540063..173eb2e 100644 --- a/quartz/util/path.ts +++ b/quartz/util/path.ts @@ -52,7 +52,7 @@ export function slugifyFilePath(fp: FilePath, excludeExt?: boolean): FullSlug { let slug = withoutFileExt .split("/") - .map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent")) // slugify all segments + .map((segment) => segment.replace(/\s/g, "-").replace(/%/g, "-percent").replace(/\?/g, "-q")) // slugify all segments .join("/") // always use / as sep .replace(/\/$/, "") // remove trailing slash From 6914d4b40caff901ccf3e9d9113c15129a68a80c Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sun, 17 Sep 2023 21:20:09 +0200 Subject: [PATCH 12/19] docs: fix intra page links --- docs/features/explorer.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/features/explorer.md b/docs/features/explorer.md index 8a9f506..76d04c6 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -42,7 +42,7 @@ Want to customize it even more? - Removing table of contents: remove `Component.Explorer()` from `quartz.layout.ts` - (optional): After removing the explorer component, you can move the [[table of contents | Table of Contents]] component back to the `left` part of the layout -- Changing `sort`, `filter` and `map` behavior: explained in [[explorer#Advanced customization]] +- Changing `sort`, `filter` and `map` behavior: explained in [[#Advanced customization]] - Component: - Wrapper (Outer component, generates file tree, etc): `quartz/components/Explorer.tsx` - Explorer node (recursive, either a folder or a file): `quartz/components/ExplorerNode.tsx` @@ -85,7 +85,7 @@ Component.Explorer({ --- -You can pass your own functions for `sortFn`, `filterFn` and `mapFn`. All functions will be executed in the order provided by the `order` option (see [[explorer#Customization]]). These functions behave similarly to their `Array.prototype` counterpart, except they modify the entire `FileNode` tree in place instead of returning a new one. +You can pass your own functions for `sortFn`, `filterFn` and `mapFn`. All functions will be executed in the order provided by the `order` option (see [[#Customization]]). These functions behave similarly to their `Array.prototype` counterpart, except they modify the entire `FileNode` tree in place instead of returning a new one. For more information on how to use `sort`, `filter` and `map`, you can check [Array.prototype.sort()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort), [Array.prototype.filter()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) and [Array.prototype.map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map). @@ -189,7 +189,7 @@ Component.Explorer({ ### Putting it all together -In this example, we're going to customize the explorer by using functions from examples above to [[explorer#Add emoji prefix | add emoji prefixes]], [[explorer#remove-list-of-elements-filter| filter out some folders]] and [[explorer#use-sort-to-put-files-first | sort with files above folders]]. +In this example, we're going to customize the explorer by using functions from examples above to [[#Add emoji prefix | add emoji prefixes]], [[#remove-list-of-elements-filter| filter out some folders]] and [[#use-sort-to-put-files-first | sort with files above folders]]. ```ts title="quartz.layout.ts" Component.Explorer({ From 4afb099bf3ec96e5d795e871ecb19575271c0714 Mon Sep 17 00:00:00 2001 From: Ben Schlegel Date: Sun, 17 Sep 2023 21:32:23 +0200 Subject: [PATCH 13/19] docs: fix examples --- docs/features/explorer.md | 18 ++++++------------ quartz/components/ExplorerNode.tsx | 2 +- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/docs/features/explorer.md b/docs/features/explorer.md index 76d04c6..cb63e40 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -145,25 +145,19 @@ Component.Explorer({ ### Remove list of elements (`filter`) -Using this example, you can remove elements from your explorer by providing a list of folders/files using the `list` array. +Using this example, you can remove elements from your explorer by providing an array of folders/files using the `omit` set. ```ts title="quartz.layout.ts" Component.Explorer({ filterFn: (node) => { - // list containing names of everything you want to filter out - const list = ["authoring content", "building your", "tags", "hosting"] - - for (let listNodeName of list) { - if (listNodeName.toLowerCase() === node.name.toLowerCase()) { - return false // Found a match, so return false to filter out the node - } - } - return true // No match found, so return true to keep the node + // set containing names of everything you want to filter out + const omit = new Set(["authoring content", "tags", "hosting"]) + return omit.has(node.name.toLowerCase()) }, }) ``` -You can customize this by changing the entries of the `list` array. Simply add all folder or file names you want to remove to the array (case insensitive). +You can customize this by changing the entries of the `omit` set. Simply add all folder or file names you want to remove. ## Advanced examples @@ -224,7 +218,7 @@ To fix this, we just changed around the order and apply the `sort` function befo > You can then import them like this: > > ```ts title="quartz.layout.ts" -> import { mapFn, filterFn, sortFn } from "./path/to/your/functions" +> import { mapFn, filterFn, sortFn } from "./functions.ts" > Component.Explorer({ > mapFn: mapFn, > filterFn: filterFn, diff --git a/quartz/components/ExplorerNode.tsx b/quartz/components/ExplorerNode.tsx index f8b99f0..fd0c082 100644 --- a/quartz/components/ExplorerNode.tsx +++ b/quartz/components/ExplorerNode.tsx @@ -82,7 +82,7 @@ export class FileNode { /** * Filter FileNode tree. Behaves similar to `Array.prototype.map()`, but modifies tree in place - * @param mapFn function to filter tree with + * @param mapFn function to use for mapping over tree */ map(mapFn: (node: FileNode) => void) { mapFn(this) From 6a2e0b3ad3a928247a03a76817d239e61cce0fe0 Mon Sep 17 00:00:00 2001 From: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Sun, 17 Sep 2023 22:04:44 +0200 Subject: [PATCH 14/19] fix: bad visibility for last explorer item (#478) * fix: bad visibility for last explorer item * feat(explorer): add pseudo element for observer --- quartz/components/Explorer.tsx | 3 ++- quartz/components/scripts/explorer.inline.ts | 25 ++++++++++++++++++-- quartz/components/styles/explorer.scss | 9 +++++++ 3 files changed, 34 insertions(+), 3 deletions(-) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 346bd75..0bdb5a6 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -95,8 +95,9 @@ export default ((userOpts?: Partial) => {
    -
      +
        +
    diff --git a/quartz/components/scripts/explorer.inline.ts b/quartz/components/scripts/explorer.inline.ts index 8073979..2b7df7d 100644 --- a/quartz/components/scripts/explorer.inline.ts +++ b/quartz/components/scripts/explorer.inline.ts @@ -3,6 +3,18 @@ import { FolderState } from "../ExplorerNode" // Current state of folders let explorerState: FolderState[] +const observer = new IntersectionObserver((entries) => { + // If last element is observed, remove gradient of "overflow" class so element is visible + const explorer = document.getElementById("explorer-ul") + for (const entry of entries) { + if (entry.isIntersecting) { + explorer?.classList.add("no-background") + } else { + explorer?.classList.remove("no-background") + } + } +}) + function toggleExplorer(this: HTMLElement) { // Toggle collapsed state of entire explorer this.classList.toggle("collapsed") @@ -101,8 +113,10 @@ function setupExplorer() { ) as HTMLElement // Get corresponding content
      tag and set state - const folderUL = folderLi.parentElement?.nextElementSibling as HTMLElement - setFolderState(folderUL, folderUl.collapsed) + const folderUL = folderLi.parentElement?.nextElementSibling + if (folderUL) { + setFolderState(folderUL as HTMLElement, folderUl.collapsed) + } }) } else { // If tree is not in localStorage or config is disabled, use tree passed from Explorer as dataset @@ -113,6 +127,13 @@ function setupExplorer() { window.addEventListener("resize", setupExplorer) document.addEventListener("nav", () => { setupExplorer() + + const explorerContent = document.getElementById("explorer-ul") + // select pseudo element at end of list + const lastItem = document.getElementById("explorer-end") + + observer.disconnect() + observer.observe(lastItem as Element) }) /** diff --git a/quartz/components/styles/explorer.scss b/quartz/components/styles/explorer.scss index 4b25a55..776a5ae 100644 --- a/quartz/components/styles/explorer.scss +++ b/quartz/components/styles/explorer.scss @@ -131,3 +131,12 @@ div:has(> .folder-outer:not(.open)) > .folder-container > svg { .folder-icon:hover { color: var(--tertiary); } + +.no-background::after { + background: none !important; +} + +#explorer-end { + // needs height so IntersectionObserver gets triggered + height: 1px; +} From 0d3cf2922618774fc397dca8cb92fcf76fb0db02 Mon Sep 17 00:00:00 2001 From: Ben Schlegel <31989404+benschlegel@users.noreply.github.com> Date: Mon, 18 Sep 2023 23:32:00 +0200 Subject: [PATCH 15/19] docs: fix explorer example (#483) --- docs/features/explorer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/explorer.md b/docs/features/explorer.md index cb63e40..6f941b8 100644 --- a/docs/features/explorer.md +++ b/docs/features/explorer.md @@ -152,7 +152,7 @@ Component.Explorer({ filterFn: (node) => { // set containing names of everything you want to filter out const omit = new Set(["authoring content", "tags", "hosting"]) - return omit.has(node.name.toLowerCase()) + return !omit.has(node.name.toLowerCase()) }, }) ``` From cc31a40b0cb53cba7f51187cb6d68076c3f54c0f Mon Sep 17 00:00:00 2001 From: David Fischer Date: Tue, 19 Sep 2023 18:25:51 +0200 Subject: [PATCH 16/19] feat: support changes in system theme (#484) * feat: support changes in system theme * fix: run prettier * fix: add content/.gitkeep --- quartz/components/scripts/darkmode.inline.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/quartz/components/scripts/darkmode.inline.ts b/quartz/components/scripts/darkmode.inline.ts index e16f4f8..c42a367 100644 --- a/quartz/components/scripts/darkmode.inline.ts +++ b/quartz/components/scripts/darkmode.inline.ts @@ -20,4 +20,13 @@ document.addEventListener("nav", () => { if (currentTheme === "dark") { toggleSwitch.checked = true } + + // Listen for changes in prefers-color-scheme + const colorSchemeMediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + colorSchemeMediaQuery.addEventListener("change", (e) => { + const newTheme = e.matches ? "dark" : "light" + document.documentElement.setAttribute("saved-theme", newTheme) + localStorage.setItem("theme", newTheme) + toggleSwitch.checked = e.matches + }) }) From 1bf7e3d8b3966590ebfa3418d6fb2ce6a520c846 Mon Sep 17 00:00:00 2001 From: Jacky Zhao Date: Tue, 19 Sep 2023 10:22:39 -0700 Subject: [PATCH 17/19] fix(nit): make defaultOptions on explorer not a function --- quartz/components/Explorer.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/quartz/components/Explorer.tsx b/quartz/components/Explorer.tsx index 0bdb5a6..8597075 100644 --- a/quartz/components/Explorer.tsx +++ b/quartz/components/Explorer.tsx @@ -6,7 +6,7 @@ import script from "./scripts/explorer.inline" import { ExplorerNode, FileNode, Options } from "./ExplorerNode" // Options interface defined in `ExplorerNode` to avoid circular dependency -const defaultOptions = (): Options => ({ +const defaultOptions = { title: "Explorer", folderClickBehavior: "collapse", folderDefaultState: "collapsed", @@ -23,11 +23,12 @@ const defaultOptions = (): Options => ({ } }, order: ["filter", "map", "sort"], -}) +} satisfies Options + export default ((userOpts?: Partial) => { function Explorer({ allFiles, displayClass, fileData }: QuartzComponentProps) { // Parse config - const opts: Options = { ...defaultOptions(), ...userOpts } + const opts: Options = { ...defaultOptions, ...userOpts } // Construct tree from allFiles const fileTree = new FileNode("") From d676a6a37e2e909a94191761b03ebad95d53b62b Mon Sep 17 00:00:00 2001 From: afonsofrancof Date: Tue, 19 Sep 2023 19:37:06 +0100 Subject: [PATCH 18/19] Quartz sync: Sep 19, 2023, 7:37 PM --- bun.lockb | Bin 0 -> 199264 bytes content/.gitkeep | 0 content/CP/T - Aula 2 - 19 Setembro.md | 7 + content/MFES/MFES - UC Details.md | 37 ++++ content/MFES/T - Aula 2 - 18 Setembro 2023.md | 121 +++++++++++++ content/MFES/TP - Aula 1 - 18 Setembro.md | 88 ++++++++++ content/RAS/T - Aula 2 - 19 Setembro 2023.md | 159 ++++++++++++++++++ content/index.md | 10 ++ package.json | 1 + quartz.config.ts | 2 +- 10 files changed, 424 insertions(+), 1 deletion(-) create mode 100755 bun.lockb delete mode 100644 content/.gitkeep create mode 100644 content/CP/T - Aula 2 - 19 Setembro.md create mode 100644 content/MFES/MFES - UC Details.md create mode 100644 content/MFES/T - Aula 2 - 18 Setembro 2023.md create mode 100644 content/MFES/TP - Aula 1 - 18 Setembro.md create mode 100644 content/RAS/T - Aula 2 - 19 Setembro 2023.md create mode 100644 content/index.md diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..ecac4f9d2c90baf1996616b8e415d932f1724cd7 GIT binary patch literal 199264 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 pT|1v_*oZ-6#U7P8 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 + +The Boolean satisfiability (SAT) problem: + +Find an assignment to the propositional variables of the formula such that the formula evaluates to TRUE, or prove that no such assignment exists. + + +- SAT is an NP-complete decision problem. + - SAT was the first problem to be shown NP-complete. + - There are no known polynomial time algorithms for SAT. + + +Usually SAT solvers deal with formulas in conjunctive normal form (CNF) +- **literal**: propositional variable or its negation A, ¬A, B, ¬B, C, ¬C +- **clause**: disjuntion of literals. (A _ ¬B _ C) +- **conjunctive normal form**: conjuction of clauses. (A _ ¬B _ C) ^ (B _ ¬A) ^ ¬C + + > [!info]+ Cook's theorem(1971) + > SAT is NP-complete + + +## 1.2 Proposicional Logic (PL) + +>[!note] Nota +>Esta secção basicamente só contém revisão de conceitos. Aconselha-se a ver a coisa rapidamente, porque é só a formalidade de lógica escrita por extenso. + +Let $A$ be an assignment and let $F$ be a formula. If $A(F) = 1$, then we say **$F$ holds under assignment**, or **$A$ models $F$.** +We write A $\models F$ iff $A(F)=1$, and $A \not \models F$ iff $A(F) = 0$. + + +An assignment is a function $A$ : $V_{prop} \implies {0,1}$ , that assigns to every +propositional variable a truth value. An assignment $A$ naturally extends to all formulas, $A$ : **Form** $\implies {0,1}$. The truth value of a formula is computed using **truth tables**: + +| F | $A$ | $B$ | $\neg A$ | $A \land B$ | $A \lor B$ | $A \implies B$ | $A \iff B$ | $\bot$ | $\top$ | +| --------- | --- | --- | -------- | ----------- | ---------- | -------------- | ---------- | ------ | ------ | +| $A_1 (F)$ | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | +| $A_2 (F)$ | 0 | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | +| $A_3 (F)$ | 1 | 1 | 0 | 1 | 1 | 1 | 1 | 0 | 1 | +| $A_4 (F)$ | 1 | 0 | 0 | 0 | 1 | 0 | 0 | 0 | 1 | + + + +A formula $F$ is: +1. **valid** iff it holds under every assignment. We write $\models F$. A valid formula is called a *tautology*. +2. **satisfiable** iff it folds (true) under some assignment. +3. **unsatisfiable** iff it holds under no assignment. An unsatisfiable formula is called a *contradiction*. +4. **refutable** iff it is not valid. + + > [!tip]+ Proposition + > $F$ is **valid** iff $\neg F$ is **unsatisfiable**. + + +- $F \models G$ iff for every assignment $A$, if $A \models F$ then $A \models G$. We say $G$ is a **consequence** of $F$. +- $F \equiv G$ iff $F \models G$ and $G \models F$. We say $F$ and $G$ are **equivalent**. +- Let $\Gamma = { F_1, F_2, F_3,... }$ be a set of formulas. + - $A \models \Gamma$ iff $A \models F_i$ for each formula $F_i$ in $\Gamma$. We say $A$ models $\Gamma$. + - $\Gamma \models G$ iff $A \models \Gamma$ implies $A \models G$ for every assignment $A$. We say $G$ is a **consequence** of $\Gamma$. + + > [!tip]+ Proposition + > - $F \models G$ iff $\models F \implies G$. + > - $\Gamma \models G$ and $\Gamma$ finite iff $\models \land \Gamma \implies G$. + > + + - $\Gamma$ is *consistent* or *satisfiable* iff there is an assignment that models $\Gamma$. + - We say that $\Gamma$ is inconsistent or unsatisfiable iff there is not consistent and denote this by $\Gamma \models \bot$. + + > [!tip]+ Proposition + > - {$F, \neg F$} $\models \bot$ + > - If $\Gamma \models \bot$ and $\Gamma \subseteq \Gamma '$, then $\Gamma ' \models \bot$ + > - $\Gamma \models F$ iff $\Gamma, \neg F \models \bot$ + +- Formula $G$ is a subformula of formula F if it occurs syntactically within F +- Formula G is a strict subformula of F if G is a subformula of $F$ and $G \neg = F$ + + +**Basic Equivalences:** +1. $\neg \neg A \equiv A$ +2. $A \lor A \equiv A$ +3. $A \land A \equiv A$ +4. $A \land \neg A \equiv \bot$ +5. $A \lor \neg A \equiv \top$ +6. $A \lor B \equiv B \lor A$ +7. $A \land B \equiv B \land A$ +8. $A \land \top \equiv A$ +9. $A \lor \top \equiv \top$ +10. $A \land \bot \equiv \bot$ +11. $A \lor \bot \equiv A$ +12. $A \land (B \lor A) \equiv A$ +13. $A \land (B \lor C) \equiv (A \land B) \lor (A \land C)$ +14. $A \lor (B \land C) \equiv (A \lor B) \land (A \lor C)$ +15. $\neg (A \lor B) \equiv \neg A \land \neg B$ +16. $\neg (A \land B) \equiv \neg A \lor \neg B$ +17. $A \implies B \equiv \neg A \lor B$ +18. $A \iff B \equiv (A \implies B) \land (B \implies A)$ + + + + +# 2. SAT Solvers +- There are several techniques and algorithms for SAT solving. +- Usually SAT solvers receive as input a formula in a specific syntatical format. +- SAT solvers deal with formulas in **conjunctive normal form (CNF)**. + +- Most current state-of-the-art SAT solvers are based on the **Davis-Putnam-Logemann-Loveland (DPLL) framework**. + +## 2.1 DPLL Framework +The idea is to **incrementally construct an assignment compatible with a CNF**, propagating the implications of the decisions made that are easy to detect and simplifying the clauses. + +A CNF is satisfied by an assignment if all its clauses are satisfied. And a clause is satisfied if at least one of its literals is satisfied. diff --git a/content/MFES/TP - Aula 1 - 18 Setembro.md b/content/MFES/TP - Aula 1 - 18 Setembro.md new file mode 100644 index 0000000..05cdc0e --- /dev/null +++ b/content/MFES/TP - Aula 1 - 18 Setembro.md @@ -0,0 +1,88 @@ +## Ficha 1 + +> [!help]+ Ex 1 (Configuração de produtos) +> Certos produtos, como é o caso dos automóveis, são altamente personalizáveis, mas podem haver dependências entre as configurações. Os clientes podem não estar cientes de todas essas dependências, e poderão escolher opções de configuração inconsistentes. +> Como são muitas configurações e muitas dependências, podemos usar um SAT solver para verificar se o cliente escolhe opções de configuração consistentes. Para isso, podemos seguir os seguintes passos: +> +> 1. Codificar as dependências entre configurações como uma formula proposicional $\psi$ +> 2. Codificar as opções selecionadas pelo cliente como uma fórmula proposicional $\phi$. +> 3. Usar o SAT solver para verificar se $\psi \land \phi$ não é contraditório. +> +> Considere agora a seguinte dependência entre as configurações disponíveis para a personalização de um automóvel: +> +> *"O ar condicionado Thermotronic comfort requer uma bateria de alta capacidade, exceto quando combinado com motores a gasolina de 3,2 litros."* +> +> Será que um cliente pode escolher o ar condicionado Thermotronic comfort, uma bateria de pequena capacidade e não escolher o motor de 3,2 litros? Como poderia responder a esta pergunta com a ajuda de um SAT solver? +> +> > [!tip]- Resolução +> > Legenda de variáveis utilizadas: +> > A - ar condicionado +> > B - bateria alta capacidade +> > C - motor 3,2 L +> > +> >$A \land \neg B \implies G \equiv \neg (A \land \neg B) \lor G \equiv \neg A \lor B \lor G$ +> > +> >$(\neg A \lor B \lor G) \land A \land \neg B \land \neg G$ ----- SAT? +> > +> + + +> [!help]+ Ex 3 (Configuração de computadores) +> Uma loja de eletrónica permite aos seus clientes personalizar o seu computador, escolhendo entre dois modelos de motherboards e dois modelos de monitor. Cada computador tem que ter obrigatoriamente uma única motherboard, um único CPU, uma única placa gráfica e uma única memória RAM. O computador poderá ou não ter um monitor. A personalização do computador deverá obedecer às regras: +> 1. A motherboard MB1 quando combinada com a placa gráfica PG1, obriga à utilização da RAM1; +> 2. A placa gráfica PG1 precisa do CPU1, exceto quando combinada com a memória RAM2; +> 3. O CPU2 só pode estar instalado na motherboard MB2; +> 4. O monitor MON1 para poder funcionar precisa da placa gráfca PG1 e da memória RAM2. +> +>Codifique este problema em lógica proposicional. Assinale claramente o que denota cada variável proposicional que introduzir e escreva um conjunto de fórmulas proposicionais adequado à sua modelação. +> +> > [!hint]- Resolução +> > Legenda de variáveis utilizadas: +> > $CPU_1; CPU_2; RAM_1; RAM_2; MB_1; MB_2; PG_1; PG_2; MON_1; MON_2$ +> > +> > Problema modelado: +> > 1. $CPU_1 \lor CPU_2$ +> > 2. $CPU_1 \implies \neg CPU_2$ +> > 3. $RAM_1 \lor RAM_2$ +> > 4. $RAM_1 \implies \neg RAM_2$ +> > 5. $MB_1 \lor MB_2$ +> > 6. $MB_1 \implies \neg MB_2$ +> > 7. $PG_1 \lor PG_2$ +> > 8. $PG_1 \implies \neg PG_2$ +> > +> > 9. $MB_1 \land PG_1 \implies RAM_1$ +> > 10. $PG_1 \implies CPU_1 \lor RAM_2 \equiv PG_1 \land \neg RAM_2 \implies CPU_1$ +> > 11. $CPU_2 \implies MB_2$ +> > 12. $MON_1 \implies PG_1 \land RAM_2$ +> +> Indique, apenas por palavras, como poderia usar um SAT solver para testar as consistências deste conjunto de regras. +> +> Indique, apenas por palavras, como usaria um SAT solver para se pronuncias quanto à velocidade das seguintes afirmações: +> (a) O monitor MON1 só poderá ser usado com uma motherboard MB1. +> > [!hint]- Resolução +> > ??? $T \models MON_1 \implies MB_1$ +> > $T, \neg (MON_1 \implies MB_1) UNSAT$ +> > > [!info] $T \models F$ sse $T, \neg F UNSAT$ +> +> (b) Um cliente pode personalizar o seu computador da seguinte forma: uma motherboard MB2, o CPU1, a placa gráfica PG2 e a memória RAM1. +> >[!hint]- Resolução +> >$T, MB_2 \land CPU_1 \land PG_2 \land RAM_1$ ---- SAT? + +> [!help]+ Ex 2 (Distribuição de Gabinetes) +> Considere que temos 3 gabinetes e queremos distribuir 4 pessoas (Ana=1, Nuno=2, Pedro=3 e Maria=4) por esses gabinetes. Para codificar este problema em lógica proposicional, , vamos ter um conjunto de variáveis proposicionais $x_{p,g}$ com a seguinte semântica: +> $x_{p,g}$ é verdade sse a pessoa $p$ ocupa o gabinete $g$, com $p=1..4$ e $g=1..3$ +> +> Considere que forem estipuladas as seguintes regras de ocupação de gabinetes: +> 1. Cada pessoa ocupa um único gabinete. +> 2. O Nuno e o Pedro não podem partilhar gabinete. +> 3. Se a Ana ficar sozinha num gabinete, então o Pedro também terá que ficar sozinho num gabinete. +> 4. Cada gabinete só pode acomodar, no máximo, 2 pessoas. +> +> Escreve um conjunto de fórumlas proposicionais adequado à modelação destas regras. +> +> >[!tip]- Resolução (por passos) +> >**Cada pessoa ocupa só um único gabinete.** +> > 1. para cada pessoa p = 1..4 +> > 2. pelo menos um gabinete: $x_{p1} \lor x_{p2} \lor x_{p3}$ +> > 3. no máximo 1 gabinete: $x_{p1} \implies \neg x_{p2} \land x_{p3} \equiv x_{p1} \implies \neg (x_{p2} \lor x_{p3} ) \equiv x_{p2} \implies \neg x_{p3}$ +> > 4. diff --git a/content/RAS/T - Aula 2 - 19 Setembro 2023.md b/content/RAS/T - Aula 2 - 19 Setembro 2023.md new file mode 100644 index 0000000..27517ac --- /dev/null +++ b/content/RAS/T - Aula 2 - 19 Setembro 2023.md @@ -0,0 +1,159 @@ +## Contents +1. [[T - Aula 2 - 19 Setembro 2023#Definition of Requirement|Definition of requirement]] +2. [[T - Aula 2 - 19 Setembro 2023#Functional requirements|Functional requirements]] +3. [[T - Aula 2 - 19 Setembro 2023#Non-functional requirements|Non-functional requirements]] +4. [[T - Aula 2 - 19 Setembro 2023#4. User and system requirements|User and system requirements]] +5. [[T - Aula 2 - 19 Setembro 2023#Recap|Recap]] + +## 1. Definition of Requirement +The requirements express the users necessities and restrictions that are +placed on the system. + +A **requirement** is a capacity that a system must possess, to satisfy the +users necessities. + +The IEEE 610.12-1990 standard definition divides requirements into +two alternatives: +1. user needs +2. system capacities + +Requirements can be divided into: +1. functional requirements +2. non-functional requirements + +A r**equirement that for a stakeholder** can be perceived +as being **functional**, can be considered as **non-functional** +for **a different one**. + +> [!info] A candidate requirement is a requirement that was identified by some elicitation technique. + + +## 2. Functional requirements + +> [!tip] Definition +> A functional requirement describes a functionality to be made +available to the users of the system. + +- This type of requirements should not mention any technological issue. +- Ideally, functional requirements must be independent of design and +implementation aspects. + +> [!note]- Note: Cohesion and Completion +> Collectively, the set of functional requirements must be complete and +coherent. The set is: +> - *coherent*, if there are no contradictions among its elements; +> - *complete*, if it considers all the necessities that the client wishes to see +satisfied. +> +> Defining complete requirements is the most difficult attribute to +achieve or evaluate. + +> [!note]- Note: Implicit and Explicit Requirements +> Implicit requirements are referred like that because the client may not be aware about these requirements. +> +> An **implicit requirement** is a requirement included by the development team, based on the domain knowledge that it possesses, in spite of not having been explicitly requested by the stakeholders. +> +> An **explicit requirement** refers to a requirement that was requested by the clients and that is represented in the documentation. + + +## 3. Non-functional requirements + +> [!tip] Definition +> A non-functional requirement corresponds to a set of restrictions +imposed on the system to be developed. + +- Alternative terms: quality requirement or quality attribute +- It establishes how attractive, fast, or reliable the system is. +- It includes time constraints, restrictions in the development process, or +adoption of standards. + +> [!example]- Example: Technological restriction +> The product must be developed in C++. + +> [!info] **A non-functional requirement does not change the essence of the system functionalities.** + + +- Non-functional requirements are applicable to the system as a whole +and not just to some of its parts, as, generally, non-functional requirements **cannot be modularized**. + +- The non-functional requirements are frequently **emergent properties** of the system at hand. *An emergent property of a system is a property that can be associated with the system as a whole, but not individually to each of its components.* + - *Reliability* is a good example of an emergent property. + + +> [!info] **Non-functional requirements are crucial to decide the system architecture.** + + + +### 3.1 Classification of non-functional requirements + +> [!tip]- Classification: Sommerville (2010) +> - **product requirements**: characterise aspects of the behaviour of the system itself (*reliability, performance, efficiency, portability, usability, testability, and readability*). +> - **organisational requirements**: come from strategies and procedures established in the context of the manufacturing process of the system or the client organisation (process standards and implementation requirements). +> - **external requirements**: have origin in external factors to the system and the development process (interoperability, legal, and ethical requirements). + +> [!tip]- Classification: Roberson and Roberson (2006) +> - **appearance**: the aspect and the aesthetics of the system; +> - **usability**: the easiness of utilization of the system and everything that permits a more friendly user experience; +> - **performance**: aspects of speed, real-time, storage capacity, and execution correction; +> - **operational**: characteristics about what the system must do to work correctly in the environment where it is inserted; +> - **maintenance and support**: attributes that allow the system to be repaired or improved and new functionalities to be added; +> - **security**: issues related to access, confidentiality, protection, and integrity of the data; +> - **cultural and political**: factors related to the stakeholders culture and habits; +> - **legal**: laws that apply to the system so that it can operate. + +#### 3.1.1 Usability + - **Ease of use** is related to the efficiency of utilizing a system and with the mechanisms that reduce the errors made by the users. + - **Personalization** is associated with the capacity of adapting the system to the tastes of the users. + - **Ease of learning** is concerned with the way users are trained to use the system. + - **Accessibility** indicates how easy it is to use the system, for people who +are somehow physically or mentally disabled. + +> [!example]- Example: Usability requirements +> 1. The product shall be easy to use for illiterate persons. +> 2. The product shall be intuitive to use for children with 4 years old. +> 3. The product shall be usable by visually impaired persons. + +#### 3.1.2 Maintenance and support +- System maintenance is divided into four types: preventive, corrective, perfective, and adaptive. +- **Modifiability** is related to maintenance and is dependent on how easy it is to locate the components that must be changed. + +> [!example]- Example: Maintenance and support requirements +> 1. The source code of the product programs should contain comments. +> 2. The product must be prepared to be translated to any language. + +#### 3.1.3 Legal +> [!example]- Example: Legal requirements +> 1. The product shall be aligned with the PMBoK guide. +> 2. The product shall be certified by the Taxing Authority. +> 3. The product shall fulfil with the copyright. + + + +## 4. User and system requirements + +A **user requirement** represents: +1. a functionality that the system is expected to provide to its users; +2. a restriction that is applicable to the operation of that system. + +- These requirements are related to the problem domain. +- They are normally expressed without great mathematical rigour, using natural language and informal diagrams. + + +A **system requirement** constitutes a more detailed specification of a requirement, being generally a formal model of the system. These requirements are oriented towards the solution domain, and are documented in a more technical language than the one +adopted for the user requirements. + +### 4.1 Problem domain +- The requirements result from necessities that exist in the problem domain to be addressed. +- User requirements should be described with the problem domain terminology. + + + + + +# Recap +1. Requirements represent the necessities of the users and the constraints that are applied to a system and that must be considered throughout the development. +2. The requirements can be classified, according to a first criterion, as either functional or non-functional. +3. Non-functional requirements are divided in 8 different types: appearance, usability, performance, operational, maintenance and support, security, cultural and political, and legal. +4. The requirements can be designated as either user or system requirements. +5. User requirements are related to the problem domain and are usually expressed in a natural language. +6. A system requirement is oriented towards the solution domain and is a detailed specification of a requirement, generally in the form of a formal model of the system. diff --git a/content/index.md b/content/index.md new file mode 100644 index 0000000..af937a3 --- /dev/null +++ b/content/index.md @@ -0,0 +1,10 @@ +Basicamente, isto é uma sopa de letras com alguma lógica por detrás (mas não muita). Abandonem as expectativas. :) + +## Conteúdo +### Ano 1 (Mestrado) +1. [[MFES - UC Details| (MFES) Métodos Formais de Engenharia de Software]] +2. (ASCN) Aplicações e Serviços de COmpitação em Nuvem +3. (RAS) Requisitos e Arquiteturas de Software +4. (CP) Computação Paralela +5. (DAA) Dados e Aprendizagem Automática +6. (ESR) Engenharia de Serviços em Rede diff --git a/package.json b/package.json index 0a2085c..2c4edfb 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "remark-smartypants": "^2.0.0", "rimraf": "^5.0.1", "serve-handler": "^6.1.5", + "shiki": "^0.14.4", "source-map-support": "^0.5.21", "to-vfile": "^7.2.4", "toml": "^3.0.0", diff --git a/quartz.config.ts b/quartz.config.ts index f677a18..5d8a1f9 100644 --- a/quartz.config.ts +++ b/quartz.config.ts @@ -9,7 +9,7 @@ const config: QuartzConfig = { analytics: { provider: "plausible", }, - baseUrl: "quartz.jzhao.xyz", + baseUrl: "notes.flup.dev", ignorePatterns: ["private", "templates", ".obsidian"], defaultDateType: "created", theme: { From 362f7d9b4fb8bddec83a7352c1aab120e1938aa0 Mon Sep 17 00:00:00 2001 From: afonsofrancof Date: Tue, 19 Sep 2023 19:39:11 +0100 Subject: [PATCH 19/19] Quartz sync: Sep 19, 2023, 7:39 PM --- content/{ => Notes}/CP/T - Aula 2 - 19 Setembro.md | 0 content/{ => Notes}/MFES/MFES - UC Details.md | 0 content/{ => Notes}/MFES/T - Aula 2 - 18 Setembro 2023.md | 0 content/{ => Notes}/MFES/TP - Aula 1 - 18 Setembro.md | 0 content/{ => Notes}/RAS/T - Aula 2 - 19 Setembro 2023.md | 0 content/{index.md => README.md} | 0 6 files changed, 0 insertions(+), 0 deletions(-) rename content/{ => Notes}/CP/T - Aula 2 - 19 Setembro.md (100%) rename content/{ => Notes}/MFES/MFES - UC Details.md (100%) rename content/{ => Notes}/MFES/T - Aula 2 - 18 Setembro 2023.md (100%) rename content/{ => Notes}/MFES/TP - Aula 1 - 18 Setembro.md (100%) rename content/{ => Notes}/RAS/T - Aula 2 - 19 Setembro 2023.md (100%) rename content/{index.md => README.md} (100%) diff --git a/content/CP/T - Aula 2 - 19 Setembro.md b/content/Notes/CP/T - Aula 2 - 19 Setembro.md similarity index 100% rename from content/CP/T - Aula 2 - 19 Setembro.md rename to content/Notes/CP/T - Aula 2 - 19 Setembro.md diff --git a/content/MFES/MFES - UC Details.md b/content/Notes/MFES/MFES - UC Details.md similarity index 100% rename from content/MFES/MFES - UC Details.md rename to content/Notes/MFES/MFES - UC Details.md diff --git a/content/MFES/T - Aula 2 - 18 Setembro 2023.md b/content/Notes/MFES/T - Aula 2 - 18 Setembro 2023.md similarity index 100% rename from content/MFES/T - Aula 2 - 18 Setembro 2023.md rename to content/Notes/MFES/T - Aula 2 - 18 Setembro 2023.md diff --git a/content/MFES/TP - Aula 1 - 18 Setembro.md b/content/Notes/MFES/TP - Aula 1 - 18 Setembro.md similarity index 100% rename from content/MFES/TP - Aula 1 - 18 Setembro.md rename to content/Notes/MFES/TP - Aula 1 - 18 Setembro.md diff --git a/content/RAS/T - Aula 2 - 19 Setembro 2023.md b/content/Notes/RAS/T - Aula 2 - 19 Setembro 2023.md similarity index 100% rename from content/RAS/T - Aula 2 - 19 Setembro 2023.md rename to content/Notes/RAS/T - Aula 2 - 19 Setembro 2023.md diff --git a/content/index.md b/content/README.md similarity index 100% rename from content/index.md rename to content/README.md