// do not exclude txt
(?i)!*.{md,txt}
// ignore all directories except for above
*
The two ** are also redundant. They are only needed when you have patterns like directory/*/file and directory/**/file, which will match different contents. In addition, * already matches everything, so there is no need for a separate .trash entry above it.
Basically, if a previous patterns matches something already, then you cannot “unignore” it afterwards. This is why if you put * first, then everything gets ignored at the very beginning and you’ve got nothing to work with anymore.
Taking .trash into account, you probably want this then:
// ignore .trash
.trash
// do not ignore md and txt
(?i)!*.{md,txt}
// ignore all except for above
*
Think of an Ignore Pattern as being like an access list in this regard:
The file is evaluated by the first line in the Ignore Pattern. If there’s a match (whether it’s to ignore or to unignore), the action is taken and the ignore process exits: in other words, further lines in the Ignore Pattern file are not evaluated for that file.
This continues, line by line, until there is a match. The action is taken and further lines are not evaluated.
The final, “implied” line of the Ignore Pattern is to allow everything.